diff --git a/packages/api/src/__generated__/schema.ts b/packages/api/src/__generated__/schema.ts index 83f0480b3f..77c847ef17 100644 --- a/packages/api/src/__generated__/schema.ts +++ b/packages/api/src/__generated__/schema.ts @@ -467,6 +467,8 @@ export type Query = { product: StoreProduct; /** Returns if there's a redirect for a search. */ redirect?: Maybe; + /** Returns a list of approved reviews for a specific product. */ + reviews?: Maybe; /** Returns the result of a product, facet, or suggestion search. */ search: StoreSearchResult; /** Returns a list of sellers available for a specific localization. */ @@ -504,6 +506,15 @@ export type QueryRedirectArgs = { }; +export type QueryReviewsArgs = { + after?: Maybe; + first?: Maybe; + productId: Scalars['String']; + rating?: Maybe; + sort?: Maybe; +}; + + export type QuerySearchArgs = { after?: Maybe; first: Scalars['Int']; @@ -1057,6 +1068,58 @@ export type StoreProductGroup = { skuVariants?: Maybe; }; +export type StoreProductListReviewsRange = { + __typename?: 'StoreProductListReviewsRange'; + /** Index of the first review */ + from: Scalars['Int']; + /** Index of the last review */ + to: Scalars['Int']; + /** Total number of reviews. */ + total: Scalars['Int']; +}; + +export type StoreProductListReviewsResult = { + __typename?: 'StoreProductListReviewsResult'; + /** Array of product reviews. */ + data: Array; + range: StoreProductListReviewsRange; +}; + +export const enum StoreProductListReviewsSort { + /** Sort by review rating, from lowest to highest. */ + RatingAsc = 'rating_asc', + /** Sort by review rating, from highest to lowest. */ + RatingDesc = 'rating_desc', + /** Sort by review creation date, from oldest to newest. */ + ReviewDateTimeAsc = 'reviewDateTime_asc', + /** Sort by review creation date, from newest to oldest. */ + ReviewDateTimeDesc = 'reviewDateTime_desc' +}; + +export type StoreProductReview = { + __typename?: 'StoreProductReview'; + /** Indicates if the review was approved by the store owner. */ + approved: Scalars['Boolean']; + /** Review ID. */ + id: Scalars['String']; + /** Product ID. */ + productId: Scalars['String']; + /** Review rating. */ + rating: Scalars['Int']; + /** Review creation date. */ + reviewDateTime: Scalars['String']; + /** Review author name. */ + reviewerName?: Maybe; + /** Review author ID. */ + shopperId: Scalars['String']; + /** Review content. */ + text: Scalars['String']; + /** Review title. */ + title: Scalars['String']; + /** Indicates if the review was made by a verified purchaser. */ + verifiedPurchaser: Scalars['Boolean']; +}; + /** Properties that can be associated with products and products groups. */ export type StorePropertyValue = { __typename?: 'StorePropertyValue'; diff --git a/packages/api/src/platforms/vtex/clients/commerce/types/ProductReview.ts b/packages/api/src/platforms/vtex/clients/commerce/types/ProductReview.ts index 23f9ee3acf..3d002d8063 100644 --- a/packages/api/src/platforms/vtex/clients/commerce/types/ProductReview.ts +++ b/packages/api/src/platforms/vtex/clients/commerce/types/ProductReview.ts @@ -26,12 +26,14 @@ export enum ProductReviewsInputOrderBy { locale = 'Locale', } +export type ProductReviewsInputOrderWay = 'asc' | 'desc' + export interface ProductReviewsInput { searchTerm?: string from?: number to?: number orderBy?: ProductReviewsInputOrderBy - orderWay?: 'asc' | 'desc' + orderWay?: ProductReviewsInputOrderWay status?: boolean productId?: string rating?: number diff --git a/packages/api/src/platforms/vtex/resolvers/query.ts b/packages/api/src/platforms/vtex/resolvers/query.ts index 4f607a72da..9a77d6e037 100644 --- a/packages/api/src/platforms/vtex/resolvers/query.ts +++ b/packages/api/src/platforms/vtex/resolvers/query.ts @@ -21,11 +21,16 @@ import type { QuerySellersArgs, QueryShippingArgs, QueryRedirectArgs, + QueryReviewsArgs, } from '../../../__generated__/schema' import type { CategoryTree } from '../clients/commerce/types/CategoryTree' import type { Context } from '../index' import { isValidSkuId, pickBestSku } from '../utils/sku' import type { SearchArgs } from '../clients/search' +import { + ProductReviewsInputOrderBy, + type ProductReviewsInputOrderWay, +} from '../clients/commerce/types/ProductReview' export const Query = { product: async (_: unknown, { locator }: QueryProductArgs, ctx: Context) => { @@ -335,4 +340,31 @@ export const Query = { sellers, } }, + reviews: async ( + _: unknown, + { productId, after, first, rating, sort }: QueryReviewsArgs, + ctx: Context + ) => { + const { + clients: { commerce }, + } = ctx + + const from = after ?? 0 + const to = from + (first ?? 6) + + const [orderByKey, orderWay] = sort?.split('_') as [ + keyof typeof ProductReviewsInputOrderBy, + ProductReviewsInputOrderWay, + ] + + return await commerce.reviews.list({ + productId, + from, + to, + orderBy: ProductReviewsInputOrderBy[orderByKey], + orderWay, + status: true, + rating: rating ?? undefined, + }) + }, } diff --git a/packages/api/src/typeDefs/productReview.graphql b/packages/api/src/typeDefs/productReview.graphql new file mode 100644 index 0000000000..6345cb8775 --- /dev/null +++ b/packages/api/src/typeDefs/productReview.graphql @@ -0,0 +1,84 @@ +type StoreProductReview { + """ + Review ID. + """ + id: String! + """ + Product ID. + """ + productId: String! + """ + Review rating. + """ + rating: Int! + """ + Review title. + """ + title: String! + """ + Review content. + """ + text: String! + """ + Review author name. + """ + reviewerName: String + """ + Review author ID. + """ + shopperId: String! + """ + Review creation date. + """ + reviewDateTime: String! + """ + Indicates if the review was made by a verified purchaser. + """ + verifiedPurchaser: Boolean! + """ + Indicates if the review was approved by the store owner. + """ + approved: Boolean! +} + +type StoreProductListReviewsRange { + """ + Total number of reviews. + """ + total: Int! + """ + Index of the first review + """ + from: Int! + """ + Index of the last review + """ + to: Int! +} + +type StoreProductListReviewsResult { + """ + Array of product reviews. + """ + data: [StoreProductReview!]! + range: StoreProductListReviewsRange! +} + +enum StoreProductListReviewsSort { + """ + Sort by review creation date, from newest to oldest. + """ + reviewDateTime_desc + """ + Sort by review creation date, from oldest to newest. + """ + reviewDateTime_asc + """ + Sort by review rating, from highest to lowest. + """ + rating_desc + """ + Sort by review rating, from lowest to highest. + """ + rating_asc +} diff --git a/packages/api/src/typeDefs/query.graphql b/packages/api/src/typeDefs/query.graphql index 9889b0e7ca..2a2b95be29 100644 --- a/packages/api/src/typeDefs/query.graphql +++ b/packages/api/src/typeDefs/query.graphql @@ -338,6 +338,33 @@ type Query { salesChannel: String ): SellersData @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600) + + """ + Returns a list of approved reviews for a specific product. + """ + reviews( + """ + Product Id + """ + productId: String! + """ + Reviews results sorting mode + """ + sort: StoreProductListReviewsSort = reviewDateTime_desc + """ + Reviews pagination argument, indicating how many items should be returned from the complete result list. + """ + first: Int = 6 + """ + Reviews pagination argument, indicating the cursor corresponding with the item after which the items should be fetched. + """ + after: Int = 0 + """ + Rating filter + """ + rating: Int + ): StoreProductListReviewsResult + @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600) } """ diff --git a/packages/api/test/schema.test.ts b/packages/api/test/schema.test.ts index 47fa97b71d..48bc1d81ce 100644 --- a/packages/api/test/schema.test.ts +++ b/packages/api/test/schema.test.ts @@ -67,6 +67,7 @@ const QUERIES = [ 'shipping', 'redirect', 'sellers', + 'reviews', ] const MUTATIONS = ['validateCart', 'validateSession', 'subscribeToNewsletter'] diff --git a/packages/core/test/server/index.test.ts b/packages/core/test/server/index.test.ts index 0a47f5f145..7bc2a374b3 100644 --- a/packages/core/test/server/index.test.ts +++ b/packages/core/test/server/index.test.ts @@ -71,6 +71,7 @@ const QUERIES = [ 'shipping', 'redirect', 'sellers', + 'reviews', ] const MUTATIONS = ['validateCart', 'validateSession', 'subscribeToNewsletter']