Using Reviews to Rate Products and Channels

Rating value

Let's assume the rating interface is using a system with 5 stars. The rating field of a review is a number between -100 and 100.
We first have to know how to encode the number of stars in the rating field.

Number of starsRating value
★★★★★5
★★★★☆4
★★★☆☆3
★★☆☆☆2
★☆☆☆☆1
☆☆☆☆☆0

Here, we use the rating field to represent the number of stars.
We could also use this field to store a percentage (ex: 95 would mean 95 %), or to store a like/dislike information (1 would mean like, -1 would mean dislike).

Review approval process

If you do not need any approval process, skip this part

If we have an approval process for a review to be used for a product or a channel, we model the approval process with a state machine.

First of all, we create the approved state:

POST /<project-key>/states with:

{
"key": "approved",
"type": "ReviewState",
"roles": ["ReviewIncludedInStatistics"]
}

Then we create the initial to-approve state, which has a possible transition to the approved state:

POST /<project-key>/states with:

{
"key": "to-approve",
"type": "ReviewState",
"initial": true,
"transitions": [
{
"typeId": "state",
"id": "<id-of-approved-state>"
}
]
}

Creating Reviews

Now we can create a review in the initial state to-approve:

POST /<project-key>/reviews with:

{
"key": "review-1",
"target": {
"typeId": "product",
"id": "<product-id>"
},
"rating": 4,
"state": {
"key": "to-approve"
}
}

remove the field state if you have not created any state machine first

Query which Reviews should be approved

skip this part if you do not have any approval process

We can query which reviews should be approved with a where predicate

GET /<project-key>/reviews with query parameters:

  • where=state(id in ("<id-of-to-approve-state>"))
final ReviewPagedQueryResponse reviewPagedQueryResponse = apiRoot
.reviews()
.get()
.withWhere("state(id in (\"<id-of-to-approve-state>\"))")
.executeBlocking()
.getBody();

Approving a Review

skip this part if you do not have any approval process

We can now approve the review review-1:

POST /<project-key>/reviews/key=review-1 with:

{
"version": 1,
"actions": [
{
"action": "transitionState",
"state": {
"key": "approved"
}
}
]
}

Displaying Products

We can display all products:

  • that have at least 3 stars (average rating superior to 3)
  • with facets about the number of products rated with an average in the different ranges 0 to 1 star, 1 to 2 stars, 2 to 3 stars, 3 to 4 stars and 4 to 5 stars.
  • sorted by average ratings

For that, we use the search endpoint:

GET /<project-key>/product-projections/search with query parameters:

  • filter = reviewRatingStatistics.averageRating:range (3 to *)
  • facet = reviewRatingStatistics.averageRating:range (0 to 1), (1 to 2), (2 to 3), (3 to 4), (4 to 5)
  • sort = reviewRatingStatistics.averageRating desc
ProductProjectionPagedSearchResponse productSearchWithFilterResult = apiRoot
.productProjections()
.search()
.get()
.withFilter("reviewRatingStatistics.averageRating:range (3 to *)")
.withFacet(
"reviewRatingStatistics.averageRating:range (0 to 1), (1 to 2), (2 to 3), (3 to 4), (4 to 5)"
)
.withSort("reviewRatingStatistics.averageRating desc")
.executeBlocking()
.getBody();

The response gives us the following information:

{
"offset": 0,
"count": <number of products to display>,
"total": <total number of products with an average rating superior to 3>,
"results": [
{
"id": "<product-1-id>",
[...]
"reviewRatingStatistics": {
"averageRating": 4.07037,
"highestRating": 5,
"lowestRating": 3,
"count": 1009,
"ratingsDistribution": {
"5": 254,
"4": 572,
"3": 183
}
}
},
{
"id": "<product-2-id>",
[...]
"reviewRatingStatistics": {
"averageRating": 2.97677,
"highestRating": 1,
"lowestRating": 0.2,
"count": 3875,
"ratingsDistribution": {
"4": 145,
"3": 3495,
"2": 235
}
}
},
[...]
],
"facets": {
"reviewRatingStatistics.averageRating": {
"type": "range",
"dataType": "number",
"ranges": [
{
"from": 0.0,
"to": 1.0,
"count": 0
},
{
"from": 1.0,
"to": 2.0,
"count": 15
},
{
"from": 2.0,
"to": 3.0,
"count": 78
},
{
"from": 3.0,
"to": 4.0,
"count": 242
},
{
"from": 4.0,
"to": 5.0,
"count": 145
}
]
}
}
}

Displaying one Product

The following information can be found in the JSON data of one product:

  • average rating: reviewRatingStatistics.averageRating.
    The value is already rounded to 5 decimals. Depending on your need, you may have to round it more.
  • number of reviews: reviewRatingStatistics.count.
  • distribution of ratings: reviewRatingStatistics.ratingsDistribution:
    ★★★★★: reviewRatingStatistics.ratingsDistribution.5
    ★★★★☆: reviewRatingStatistics.ratingsDistribution.4
    ★★★☆☆: reviewRatingStatistics.ratingsDistribution.3
    ★★☆☆☆: reviewRatingStatistics.ratingsDistribution.2
    ★☆☆☆☆: reviewRatingStatistics.ratingsDistribution.1
    ☆☆☆☆☆: reviewRatingStatistics.ratingsDistribution.0
    If one field for one rating does not exist, it means that there are no reviews with that rating value.

Displaying all Reviews of one Product

To retrieve all reviews for one product, sorted by rating:

GET /<project-key>/reviews with query parameter:

  • where = target(typeId = "product" and id = "<product-id>")
  • sort = rating desc
final ReviewPagedQueryResponse review = apiRoot
.reviews()
.get()
.withWhere("target(typeId = \"product\" and id = \"<product-id>\")")
.withSort("rating desc")
.executeBlocking()
.getBody();

To query only the reviews used for the rating statistics of the product:

GET <project-key>/reviews with query parameters:

  • where = target(typeId = "product" and id = "<product-id>") and includedInStatistics = true
  • sort = rating desc
final Review reviewQuery = apiRoot
.reviews()
.get()
.withWhere(
"target(typeId = \"product\" and id = \"<product-id>\") and includedInStatistics = true"
)
.withSort("rating desc")
.executeBlocking()
.getBody();