Tutorial

Using reviews to rate products and channels

Rating value

Let’s assume our 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:

#!/bin/sh
curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/states -d @- << EOF
{
  "key": "approved",
  "type": "ReviewState",
  "roles": ["ReviewIncludedInStatistics"]
}
EOF
{
  "key": "approved",
  "type": "ReviewState",
  "roles": ["ReviewIncludedInStatistics"]
}
 final StateDraftDsl approvedStateDraft = StateDraftBuilder.of("approved", StateType.REVIEW_STATE)
         .roles(asSet(StateRole.REVIEW_INCLUDED_IN_STATISTICS))
         .build();
 return client.execute(StateCreateCommand.of(approvedStateDraft));
<?php
 $stateDraft = StateDraft::ofKeyAndType('approved', 'ReviewState')
     ->setRoles([StateRole::REVIEW_INCLUDED_IN_STATISTICS]);
 $request = StateCreateRequest::ofDraft($stateDraft);
 $response = $client->execute(StateCreateRequest::ofDraft($stateDraft));
 $state = $request->mapResponse($response);

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

POST /<project-key>/states with:

#!/bin/sh
curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/states -d @- << EOF
{
  "key": "to-approve",
  "type": "ReviewState",
  "initial": true,
  "transitions": [
    {
      "typeId": "state",
      "id": "<id-of-approved-state>"
    }
  ]
}
EOF
{
  "key": "to-approve",
  "type": "ReviewState",
  "initial": true,
  "transitions": [
    {
      "typeId": "state",
      "id": "<id-of-approved-state>"
    }
  ]
}
 final StateDraftDsl stateDraft = StateDraftBuilder.of("to-approve", StateType.REVIEW_STATE)
         .initial(true)
         .transitions(asSet(approvedState.toReference()))
         .build();
 return client.execute(StateCreateCommand.of(stateDraft));
<?php
 $stateDraft = StateDraft::ofKeyAndType('to-approve', 'ReviewState');
 $stateDraft->setInitial(true)
            ->setTransitions(
                 StateReferenceCollection::of()
                     ->add($approvedState->getReference())
            );
 $request = StateCreateRequest::ofDraft($stateDraft);
 $response = $client->execute(StateCreateRequest::ofDraft($stateDraft));
 $state = $request->mapResponse($response);

Creating Reviews

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

POST /<project-key>/reviews with:

#!/bin/sh
curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/reviews -d @- << EOF
{
  "key": "review-1",
  "target": {
    "typeId": "product",
    "id": "<product-id>"
  },
  "rating": 4,
  "state": {
    "key": "to-approve"
  }
}
EOF
{
  "key": "review-1",
  "target": {
    "typeId": "product",
    "id": "<product-id>"
  },
  "rating": 4,
  "state": {
    "key": "to-approve"
  }
}
 final ResourceIdentifier<State> stateResourceIdentifier = ResourceIdentifier.ofKey(state.getKey());
 final ResourceIdentifier<Product> target = ResourceIdentifier.ofId(product.getId(), "product");
 final ReviewDraftDsl reviewDraft = ReviewDraftBuilder.ofRating(4)
         .key("review-1")
         .state(stateResourceIdentifier)
         .target(target)
         .build();
 return client.execute(ReviewCreateCommand.of(reviewDraft));
<?php
 $reviewDraft = ReviewDraft::of()
                     ->setKey('review-1')
                     ->setRating(4)
                     ->setState($state->getReference())
                     ->setTarget($product->getReference());
 $request = ReviewCreateRequest::ofDraft($reviewDraft);
 $response = $client->execute(ReviewCreateRequest::ofDraft($reviewDraft));
 $review = $request->mapResponse($response);

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:

curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/reviews?where=state%28id+in+%28%22<id-of-to-approve-state>%22%29%29
 final ReviewQuery reviewQuery = ReviewQuery.of()
         .withPredicates(m -> m.state().id().is(state.getId()));
 return client.execute(reviewQuery);
<?php
 $request = ReviewQueryRequest::of()->where('state(id in ("<id-of-to-approve-state>"))');
 $response = $request->executeWithClient($client);
 $reviews = $request->mapFromResponse($response);

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:

#!/bin/sh
curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/reviews -d @- << EOF
{
  "version": 1,
  "actions": [
    {
      "action": "transitionState",
      "state": {
        "key": "approved"
      }
    }
  ]
}
EOF
{
  "version": 1,
  "actions": [
    {
      "action": "transitionState",
      "state": {
        "key": "approved"
      }
    }
  ]
}
 final ReviewUpdateCommand reviewUpdateCommand = ReviewUpdateCommand.of(review, TransitionState.of(approvedState));
 return client.execute(reviewUpdateCommand);
<?php
 $request = ReviewUpdateRequest::ofKeyAndVersion('review-1', $review->getVersion());
 $request->addAction(TransitionStateAction::ofState(StateReference::ofKey('approved')));
 $response = $request->executeWithClient($client);
 $review = $request->mapFromResponse($response);

Displaying Products

We can display all products:

For that, we use the search endpoint:

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

curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/product-projections/search?filter=reviewRatingStatistics.averageRating%3Arange+%283+to+%2A%29&facet=reviewRatingStatistics.averageRating%3Arange+%280+to+1%29%2C+%281+to+2%29%2C+%282+to+3%29%2C+%283+to+4%29%2C+%284+to+5%29&sort=reviewRatingStatistics.averageRating+desc
 final ProductProjectionSearch productSearchWithFilter = ProductProjectionSearch.ofCurrent()
         .plusQueryFilters(m -> m.reviewRatingStatistics().averageRating().isGreaterThanOrEqualTo(BigDecimal.valueOf(3)))
         .plusFacets(m -> m.reviewRatingStatistics().averageRating().allRanges())
         .plusSort(m -> m.reviewRatingStatistics().averageRating().desc());
 return client.execute(productSearchWithFilter);
<?php
 $request = ProductProjectionSearchRequest::of()
                 ->addFilter(Filter::ofName('reviewRatingStatistics.averageRating')
                     ->setValue(FilterRangeCollection::of()->add(FilterRange::ofFromAndTo('3', '*'))))
                 ->addFacet(Facet::ofName('reviewRatingStatistics.averageRating')
                     ->setValue(
                         FilterRangeCollection::of()
                             ->add(FilterRange::of()->setFrom(0)->setTo(1))
                             ->add(FilterRange::of()->setFrom(1)->setTo(2))
                             ->add(FilterRange::of()->setFrom(2)->setTo(3))
                             ->add(FilterRange::of()->setFrom(3)->setTo(4))
                             ->add(FilterRange::of()->setFrom(4)->setTo(5))
                     ))
                 ->sort('reviewRatingStatistics.averageRating desc');
 $response = $request->executeWithClient($client);
 $products = $request->mapFromResponse($response);

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:

Displaying all Reviews of one Product

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

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

curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/reviews?where=target%28typeId+%3D+%22product%22+and+id+%3D+%22<product-id>%22%29&sort=rating+desc
 final ReviewQuery reviewQuery = ReviewQuery.of()
         .withPredicates(m -> m.target()
                 .typeId().is("product")
                 .and(m.target().id().is(product.getId()))
         )
         .plusSort(m -> m.rating().sort().desc());
 return client.execute(reviewQuery);
<?php
 $request = ReviewQueryRequest::of()
     ->where('target(typeId = "product"')
     ->where('id = "'.$product->getId().'")')
     ->sort('rating desc');
 $response = $request->executeWithClient($client);
 $products = $request->mapFromResponse($response);

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

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

curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/reviews?where=target%28typeId+%3D+%22product%22+and+id+%3D+%22<product-id>%22%29+and+includedInStatistics=true&sort=rating+desc
 final ReviewQuery reviewQuery = ReviewQuery.of()
         .withPredicates(m -> m.target()
                 .typeId().is("product")
                 .and(m.target().id().is(product.getId()))
                 .and(m.includedInStatistics().is(true)))
         .plusSort(m -> m.rating().sort().desc());
 return client.execute(reviewQuery);
<?php
 $request = ReviewQueryRequest::of()
     ->where('target(typeId = "product"')
     ->where('id = "'.$product->getId().'")')
     ->where('includedInStatistics = true')
     ->sort('rating desc');
 $response = $request->executeWithClient($client);
 $products = $request->mapFromResponse($response);
comments powered by Disqus