From 9365b57c0591e14f5945a856acfb740c1921f69f Mon Sep 17 00:00:00 2001 From: eduard13 Date: Fri, 17 Apr 2020 14:55:42 +0300 Subject: [PATCH 1/9] Adding Product and Customer Reviews GraphQl support --- .../AddRatingVotesToCustomerReviews.php | 55 ++++ .../Service/GetReviewAverageRatingService.php | 32 ++ .../ReviewGraphQl/Mapper/ReviewDataMapper.php | 36 +++ .../AggregatedReviewsDataProvider.php | 75 +++++ .../CustomerReviewsDataProvider.php | 62 ++++ .../ProductReviewsDataProvider.php | 60 ++++ .../ReviewRatingsDataProvider.php | 35 +++ .../Model/Resolver/CreateProductReview.php | 105 +++++++ .../Model/Resolver/Customer/Reviews.php | 90 ++++++ .../Model/Resolver/Product/RatingSummary.php | 79 +++++ .../Resolver/Product/Review/AverageRating.php | 69 +++++ .../Product/Review/RatingBreakdown.php | 69 +++++ .../Model/Resolver/Product/ReviewCount.php | 68 +++++ .../Model/Resolver/Product/Reviews.php | 91 ++++++ .../ProductReviewRatingValueMetadata.php | 56 ++++ .../Resolver/ProductReviewRatingsMetadata.php | 80 +++++ .../Model/Review/AddReviewToProduct.php | 159 ++++++++++ app/code/Magento/ReviewGraphQl/README.md | 3 + app/code/Magento/ReviewGraphQl/composer.json | 29 ++ app/code/Magento/ReviewGraphQl/etc/module.xml | 17 ++ .../Magento/ReviewGraphQl/etc/schema.graphqls | 78 +++++ .../Magento/ReviewGraphQl/registration.php | 9 + composer.json | 1 + .../Review/CreateProductReviewsTest.php | 209 +++++++++++++ .../GraphQl/Review/GetProductReviewsTest.php | 284 ++++++++++++++++++ ..._position_and_add_store_to_all_ratings.php | 31 ++ ..._and_add_store_to_all_ratings_rollback.php | 29 ++ 27 files changed, 1911 insertions(+) create mode 100644 app/code/Magento/Review/Model/Review/AddRatingVotesToCustomerReviews.php create mode 100644 app/code/Magento/Review/Service/GetReviewAverageRatingService.php create mode 100644 app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/DataProvider/AggregatedReviewsDataProvider.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/DataProvider/ProductReviewsDataProvider.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/DataProvider/ReviewRatingsDataProvider.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Resolver/CreateProductReview.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Resolver/Customer/Reviews.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Resolver/Product/RatingSummary.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/RatingBreakdown.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingValueMetadata.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingsMetadata.php create mode 100644 app/code/Magento/ReviewGraphQl/Model/Review/AddReviewToProduct.php create mode 100644 app/code/Magento/ReviewGraphQl/README.md create mode 100644 app/code/Magento/ReviewGraphQl/composer.json create mode 100644 app/code/Magento/ReviewGraphQl/etc/module.xml create mode 100644 app/code/Magento/ReviewGraphQl/etc/schema.graphqls create mode 100644 app/code/Magento/ReviewGraphQl/registration.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Review/CreateProductReviewsTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Review/_files/set_position_and_add_store_to_all_ratings.php create mode 100644 dev/tests/integration/testsuite/Magento/Review/_files/set_position_and_add_store_to_all_ratings_rollback.php diff --git a/app/code/Magento/Review/Model/Review/AddRatingVotesToCustomerReviews.php b/app/code/Magento/Review/Model/Review/AddRatingVotesToCustomerReviews.php new file mode 100644 index 0000000000000..c50790405a880 --- /dev/null +++ b/app/code/Magento/Review/Model/Review/AddRatingVotesToCustomerReviews.php @@ -0,0 +1,55 @@ +ratingOptionCollectionFactory = $ratingOptionCollectionFactory; + } + + /** + * Add rating votes to customer reviews + * + * @param Collection $collection + */ + public function execute(Collection $collection): void + { + $connection = $collection->getConnection(); + + foreach ($collection->getItems() as &$item) { + /** @var OptionVoteCollection $votesCollection */ + $votesCollection = $this->ratingOptionCollectionFactory->create(); + + $votesCollection->addFieldToFilter('main_table.review_id', $item->getData('review_id')); + $votesCollection->getSelect() + ->join( + ['rating' => $connection->getTableName('rating')], + 'rating.rating_id = main_table.rating_id', + ['rating_code'] + ); + $item->setRatingVotes($votesCollection); + } + } +} diff --git a/app/code/Magento/Review/Service/GetReviewAverageRatingService.php b/app/code/Magento/Review/Service/GetReviewAverageRatingService.php new file mode 100644 index 0000000000000..92175b717bcc8 --- /dev/null +++ b/app/code/Magento/Review/Service/GetReviewAverageRatingService.php @@ -0,0 +1,32 @@ +getData('value'); + } + + return $averageRating > 0 ? (float) number_format($averageRating / count($ratingVotes), 2) : 0; + } +} diff --git a/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php b/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php new file mode 100644 index 0000000000000..324775f61fa66 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php @@ -0,0 +1,36 @@ + $review->getData('title'), + 'text' => $review->getData('detail'), + 'nickname' => $review->getData('nickname'), + 'created_at' => $review->getData('created_at'), + 'rating_votes' => $review->getData('rating_votes'), + 'sku' => $review->getSku() + ]; + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/DataProvider/AggregatedReviewsDataProvider.php b/app/code/Magento/ReviewGraphQl/Model/DataProvider/AggregatedReviewsDataProvider.php new file mode 100644 index 0000000000000..5412c670b4800 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/DataProvider/AggregatedReviewsDataProvider.php @@ -0,0 +1,75 @@ +reviewDataMapper = $reviewDataMapper; + } + + /** + * Get reviews result + * + * @param ProductCollection|ReviewCollection $reviewsCollection + * + * @return array + */ + public function getData($reviewsCollection): array + { + if ($reviewsCollection->getPageSize()) { + $maxPages = ceil($reviewsCollection->getSize() / $reviewsCollection->getPageSize()); + } else { + $maxPages = 0; + } + + $currentPage = $reviewsCollection->getCurPage(); + if ($reviewsCollection->getCurPage() > $maxPages && $reviewsCollection->getSize() > 0) { + $currentPage = new GraphQlInputException( + __( + 'currentPage value %1 specified is greater than the number of pages available.', + [$maxPages] + ) + ); + } + + $items = []; + foreach ($reviewsCollection->getItems() as $item) { + $items[] = $this->reviewDataMapper->map($item); + } + + return [ + 'total_count' => $reviewsCollection->getSize(), + 'items' => $items, + 'page_info' => [ + 'page_size' => $reviewsCollection->getPageSize(), + 'current_page' => $currentPage, + 'total_pages' => $maxPages + ] + ]; + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php b/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php new file mode 100644 index 0000000000000..643ab5954641b --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php @@ -0,0 +1,62 @@ +collectionFactory = $collectionFactory; + $this->addRatingVotesToCustomerReviews = $addRatingVotesToCustomerReviews; + } + + /** + * Get customer reviews + * + * @param int $customerId + * @param int $currentPage + * @param int $pageSize + * + * @return ProductReviewsCollection + */ + public function getData(int $customerId, int $currentPage, int $pageSize): ProductReviewsCollection + { + /** @var ProductReviewsCollection $reviewsCollection */ + $reviewsCollection = $this->collectionFactory->create(); + $reviewsCollection->addCustomerFilter($customerId) + ->setPageSize($pageSize) + ->setCurPage($currentPage) + ->setDateOrder(); + $this->addRatingVotesToCustomerReviews->execute($reviewsCollection); + + return $reviewsCollection; + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/DataProvider/ProductReviewsDataProvider.php b/app/code/Magento/ReviewGraphQl/Model/DataProvider/ProductReviewsDataProvider.php new file mode 100644 index 0000000000000..635605f9091ed --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/DataProvider/ProductReviewsDataProvider.php @@ -0,0 +1,60 @@ +collectionFactory = $collectionFactory; + } + + /** + * Get product reviews + * + * @param int $productId + * @param int $currentPage + * @param int $pageSize + * + * @return Collection + */ + public function getData(int $productId, int $currentPage, int $pageSize): Collection + { + /** @var Collection $reviewsCollection */ + $reviewsCollection = $this->collectionFactory->create() + ->addStatusFilter(Review::STATUS_APPROVED) + ->addEntityFilter(Review::ENTITY_PRODUCT_CODE, $productId) + ->setPageSize($pageSize) + ->setCurPage($currentPage) + ->setDateOrder(); + $reviewsCollection->getSelect()->join( + ['cpe' => $reviewsCollection->getTable('catalog_product_entity')], + 'cpe.entity_id = main_table.entity_pk_value', + ['sku'] + ); + $reviewsCollection->addRateVotes(); + + return $reviewsCollection; + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/DataProvider/ReviewRatingsDataProvider.php b/app/code/Magento/ReviewGraphQl/Model/DataProvider/ReviewRatingsDataProvider.php new file mode 100644 index 0000000000000..a327f51c3e696 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/DataProvider/ReviewRatingsDataProvider.php @@ -0,0 +1,35 @@ + $ratingVote->getData('rating_code'), + 'value' => $ratingVote->getData('value') + ]; + } + + return $data; + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/CreateProductReview.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/CreateProductReview.php new file mode 100644 index 0000000000000..6db31f3a50f33 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/CreateProductReview.php @@ -0,0 +1,105 @@ +addReviewToProduct = $addReviewToProduct; + $this->reviewDataMapper = $reviewDataMapper; + $this->reviewHelper = $reviewHelper; + } + + /** + * Resolve product review ratings + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return array[]|Value|mixed + * + * @throws GraphQlAuthorizationException + * @throws GraphQlNoSuchEntityException + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $input = $args['input']; + $customerId = null; + + if (false !== $context->getExtensionAttributes()->getIsCustomer()) { + $customerId = (int) $context->getUserId(); + } + + if (!$customerId && !$this->reviewHelper->getIsGuestAllowToWrite()) { + throw new GraphQlAuthorizationException(__('Guest customers aren\'t allowed to add product reviews.')); + } + + $sku = $input['sku']; + $ratings = $input['ratings']; + $data = [ + 'nickname' => $input['nickname'], + 'title' => $input['summary'], + 'detail' => $input['text'], + ]; + /** @var StoreInterface $store */ + $store = $context->getExtensionAttributes()->getStore(); + $review = $this->addReviewToProduct->execute($data, $ratings, $sku, $customerId, (int) $store->getId()); + + return ['review' => $this->reviewDataMapper->map($review)]; + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Customer/Reviews.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Customer/Reviews.php new file mode 100644 index 0000000000000..8c0bca63f8efc --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Customer/Reviews.php @@ -0,0 +1,90 @@ +customerReviewsDataProvider = $customerReviewsDataProvider; + $this->aggregatedReviewsDataProvider = $aggregatedReviewsDataProvider; + } + + /** + * Resolves the customer reviews + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return array|Value|mixed + * + * @throws GraphQlInputException + * @throws GraphQlAuthorizationException + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (false === $context->getExtensionAttributes()->getIsCustomer()) { + throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); + } + + if ($args['currentPage'] < 1) { + throw new GraphQlInputException(__('currentPage value must be greater than 0.')); + } + + if ($args['pageSize'] < 1) { + throw new GraphQlInputException(__('pageSize value must be greater than 0.')); + } + + $reviewsCollection = $this->customerReviewsDataProvider->getData( + (int) $context->getUserId(), + $args['currentPage'], + $args['pageSize'] + ); + + return $this->aggregatedReviewsDataProvider->getData($reviewsCollection); + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/RatingSummary.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/RatingSummary.php new file mode 100644 index 0000000000000..56c7e7b5912f2 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/RatingSummary.php @@ -0,0 +1,79 @@ +summaryFactory = $summaryFactory; + } + + /** + * Resolves the product rating summary + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return float + * + * @throws GraphQlInputException + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ): float { + if (!isset($value['model'])) { + throw new GraphQlInputException(__('Value must contain "model" property.')); + } + + /** @var StoreInterface $store */ + $store = $context->getExtensionAttributes()->getStore(); + + /** @var Product $product */ + $product = $value['model']; + + try { + $summary = $this->summaryFactory->create()->setStoreId($store->getId())->load($product->getId()); + + return floatval($summary->getData('rating_summary')); + } catch (Exception $e) { + throw new GraphQlInputException(__('Couldn\'t get the product rating summary.')); + } + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php new file mode 100644 index 0000000000000..eb84856ba47d6 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php @@ -0,0 +1,69 @@ +getReviewAverageRatingService = $getReviewAverageRatingService; + } + + /** + * Resolves review average rating + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return float|Value|mixed + * + * @throws GraphQlInputException + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['rating_votes'])) { + throw new GraphQlInputException(__('Value must contain "rating_votes" property.')); + } + + /** @var VoteCollection $ratingVotes */ + $ratingVotes = $value['rating_votes']; + + return $this->getReviewAverageRatingService->execute($ratingVotes->getItems()); + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/RatingBreakdown.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/RatingBreakdown.php new file mode 100644 index 0000000000000..050afed45dbfb --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/RatingBreakdown.php @@ -0,0 +1,69 @@ +reviewRatingsDataProvider = $reviewRatingsDataProvider; + } + + /** + * Resolves the rating breakdown + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return array|Value|mixed + * + * @throws GraphQlInputException + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['rating_votes'])) { + throw new GraphQlInputException(__('Value must contain "rating_votes" property.')); + } + + /** @var VoteCollection $ratingVotes */ + $ratingVotes = $value['rating_votes']; + + return $this->reviewRatingsDataProvider->getData($ratingVotes->getItems()); + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php new file mode 100644 index 0000000000000..5e9fa490fcd8f --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php @@ -0,0 +1,68 @@ +review = $review; + } + + /** + * Resolves the product total reviews + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return int|Value|mixed + * + * @throws GraphQlInputException + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['model'])) { + throw new GraphQlInputException(__('Value must contain "model" property.')); + } + + /** @var Product $product */ + $product = $value['model']; + + return (int) $this->review->getTotalReviews($product->getId(), true); + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php new file mode 100644 index 0000000000000..f414e189d8b1f --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php @@ -0,0 +1,91 @@ +productReviewsDataProvider = $productReviewsDataProvider; + $this->aggregatedReviewsDataProvider = $aggregatedReviewsDataProvider; + } + + /** + * Resolves the product reviews + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return array|Value|mixed + * + * @throws GraphQlInputException + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['model'])) { + throw new GraphQlInputException(__('Value must contain "model" property.')); + } + + if ($args['currentPage'] < 1) { + throw new GraphQlInputException(__('currentPage value must be greater than 0.')); + } + + if ($args['pageSize'] < 1) { + throw new GraphQlInputException(__('pageSize value must be greater than 0.')); + } + + /** @var Product $product */ + $product = $value['model']; + $reviewsCollection = $this->productReviewsDataProvider->getData( + (int) $product->getId(), + $args['currentPage'], + $args['pageSize'] + ); + + return $this->aggregatedReviewsDataProvider->getData($reviewsCollection); + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingValueMetadata.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingValueMetadata.php new file mode 100644 index 0000000000000..e7e6574e7e7ae --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingValueMetadata.php @@ -0,0 +1,56 @@ + $item->getData('value'), 'value_id' => base64_encode($item->getData('option_id'))]; + } + + return $data; + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingsMetadata.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingsMetadata.php new file mode 100644 index 0000000000000..075666ef9a0ad --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingsMetadata.php @@ -0,0 +1,80 @@ +ratingCollectionFactory = $ratingCollectionFactory; + } + + /** + * Resolve product review ratings + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return array[]|Value|mixed + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $items = []; + /** @var StoreInterface $store */ + $store = $context->getExtensionAttributes()->getStore(); + + /** @var RatingCollection $ratingCollection */ + $ratingCollection = $this->ratingCollectionFactory->create(); + $ratingCollection->addEntityFilter(Review::ENTITY_PRODUCT_CODE) + ->setStoreFilter($store->getId()) + ->setActiveFilter(true) + ->setPositionOrder() + ->addOptionToItems(); + + foreach ($ratingCollection->getItems() as $item) { + $items[] = [ + 'id' => base64_encode($item->getData('rating_id')), + 'name' => $item->getData('rating_code'), + 'values' => $item->getData('options') + ]; + } + + return ['items' => $items]; + } +} diff --git a/app/code/Magento/ReviewGraphQl/Model/Review/AddReviewToProduct.php b/app/code/Magento/ReviewGraphQl/Model/Review/AddReviewToProduct.php new file mode 100644 index 0000000000000..1b744e717a782 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/Model/Review/AddReviewToProduct.php @@ -0,0 +1,159 @@ +productRepository = $productRepository; + $this->reviewFactory = $reviewFactory; + $this->ratingFactory = $ratingFactory; + $this->ratingOptionCollectionFactory = $ratingOptionCollectionFactory; + } + + /** + * Add review to product + * + * @param array $data + * @param array $ratings + * @param string $sku + * @param int|null $customerId + * @param int $storeId + * + * @return Review + * + * @throws GraphQlNoSuchEntityException + */ + public function execute(array $data, array $ratings, string $sku, ?int $customerId, int $storeId): Review + { + $review = $this->reviewFactory->create()->setData($data); + $review->unsetData('review_id'); + $productId = $this->getProductIdBySku($sku); + $review->setEntityId($review->getEntityIdByCode(Review::ENTITY_PRODUCT_CODE)) + ->setEntityPkValue($productId) + ->setStatusId(Review::STATUS_PENDING) + ->setCustomerId($customerId) + ->setStoreId($storeId) + ->setStores([$storeId]) + ->save(); + $this->addReviewRatingVotes($ratings, (int) $review->getId(), $customerId, $productId); + $review->aggregate(); + $votesCollection = $this->getReviewRatingVotes((int) $review->getId(), $storeId); + $review->setData('rating_votes', $votesCollection); + $review->setData('sku', $sku); + + return $review; + } + + /** + * Get Product ID + * + * @param string $sku + * + * @return int|null + * + * @throws GraphQlNoSuchEntityException + */ + private function getProductIdBySku(string $sku): ?int + { + try { + $product = $this->productRepository->get($sku, false, null, true); + + return (int) $product->getId(); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException(__('Could not find a product with SKU "%sku"', ['sku' => $sku])); + } + } + + /** + * Add review rating votes + * + * @param array $ratings + * @param int $reviewId + * @param int|null $customerId + * @param int $productId + * + * @return void + * + * @phpcs:disable Magento2.Functions.DiscouragedFunction + */ + private function addReviewRatingVotes(array $ratings, int $reviewId, ?int $customerId, int $productId): void + { + foreach ($ratings as $option) { + $ratingId = $option['id']; + $optionId = $option['value_id']; + /** @var Rating $ratingModel */ + $ratingModel = $this->ratingFactory->create(); + $ratingModel->setRatingId(base64_decode($ratingId)) + ->setReviewId($reviewId) + ->setCustomerId($customerId) + ->addOptionVote(base64_decode($optionId), $productId); + } + } + + /** + * Get review rating votes + * + * @param int $reviewId + * @param int $storeId + * + * @return OptionVoteCollection + */ + private function getReviewRatingVotes(int $reviewId, int $storeId): OptionVoteCollection + { + /** @var OptionVoteCollection $votesCollection */ + $votesCollection = $this->ratingOptionCollectionFactory->create(); + $votesCollection->setReviewFilter($reviewId)->setStoreFilter($storeId)->addRatingInfo($storeId); + + return $votesCollection; + } +} diff --git a/app/code/Magento/ReviewGraphQl/README.md b/app/code/Magento/ReviewGraphQl/README.md new file mode 100644 index 0000000000000..bf9563b87c9b2 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/README.md @@ -0,0 +1,3 @@ +# ReviewGraphQl + +**ReviewGraphQl** provides endpoints for getting and creating the Product reviews by guest and logged in customers. diff --git a/app/code/Magento/ReviewGraphQl/composer.json b/app/code/Magento/ReviewGraphQl/composer.json new file mode 100644 index 0000000000000..6e8b6f0472e45 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/composer.json @@ -0,0 +1,29 @@ +{ + "name": "magento/module-review-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/module-catalog": "*", + "magento/module-review": "*", + "magento/framework": "*", + "magento/module-store": "*", + "magento/module-graph-ql": "*" + }, + "suggest": { + "magento/module-graph-ql-cache": "*", + "magento/module-store-graph-ql": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\ReviewGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/ReviewGraphQl/etc/module.xml b/app/code/Magento/ReviewGraphQl/etc/module.xml new file mode 100644 index 0000000000000..c098ee5094760 --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/etc/module.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/app/code/Magento/ReviewGraphQl/etc/schema.graphqls b/app/code/Magento/ReviewGraphQl/etc/schema.graphqls new file mode 100644 index 0000000000000..e9b99a48bb99a --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/etc/schema.graphqls @@ -0,0 +1,78 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +interface ProductInterface { + rating_summary: Float! @doc(description: "The average of all the ratings given to the product.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Product\\RatingSummary") + review_count: Int! @doc(description: "The total count of all the reviews given to the product.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Product\\ReviewCount") + reviews( + pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once."), + currentPage: Int = 1 @doc(description: "Specifies which page of results to return."), + ): ProductReviews! @doc(description: "The list of products reviews.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Product\\Reviews") +} + +type ProductReviews { + items: [ProductReview]! @doc(description: "An array of product reviews.") + page_info: SearchResultPageInfo! @doc(description: "Metadata for pagination rendering.") +} + +type ProductReview @doc(description: "Details of a product review") { + product: ProductInterface! @doc(description: "Contains details about the reviewed product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product") + summary: String! @doc(description: "The review summary (a.k.a title") + text: String! @doc(description: "The review text.") + nickname: String! @doc(description: "The customer's nickname. Defaults to customer name if logged in.") + created_at: String! @doc(description: "Date indicating when the review was created.") + average_rating: Float! @doc(description: "The average rating for product review.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Product\\Review\\AverageRating") + ratings_breakdown: [ProductReviewRating!]! @doc(description: "An array of ratings by rating category. For example quality, price.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Product\\Review\\RatingBreakdown") +} + +type ProductReviewRating { + name: String! @doc(description: "The review rating name for example quality, price.") + value: String! @doc(description: "The rating value given by customer. Possible values by default: 1 to 5.") +} + +type Query { + productReviewRatingsMetadata: ProductReviewRatingsMetadata! @doc(description: "Metadata required by clients to render ratings & reviews section.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\ProductReviewRatingsMetadata") +} + +type ProductReviewRatingsMetadata { + items: [ProductReviewRatingMetadata!]! @doc(description: "List of product reviews sorted based on position") +} + +type ProductReviewRatingMetadata { + id: String! @doc(description: "Base 64 encoded rating id.") + name: String! @doc(description: "The review rating name for example quality, price") + values: [ProductReviewRatingValueMetadata!]! @doc(description: "List of product review ratings sorted based on position.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\ProductReviewRatingValueMetadata") +} + +type ProductReviewRatingValueMetadata { + value_id: String! @doc(description: "Base 64 encoded rating value id.") + value: String! @doc(description: "e.g Good, Perfect, 3, 4, 5") +} + +type Customer { + reviews( + pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once."), + currentPage: Int = 1 @doc(description: "Specifies which page of results to return."), + ): ProductReviews! @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Customer\\Reviews") +} + +type Mutation { + createProductReview(input: CreateProductReviewInput!): CreateProductReviewOutput! @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\CreateProductReview") +} + +type CreateProductReviewOutput { + review: ProductReview! +} + +input CreateProductReviewInput { + sku: String! @doc(description: "The SKU of the product that the review is assigned") + nickname: String! @doc(description: "The customer's nickname. Defaults to customer name if logged in.") + summary: String! @doc(description: "The review summary (a.k.a title") + text: String! @doc(description: "The review text.") + ratings: [ProductReviewRatingInput!]! @doc(description: "Ratings details by category. e.g price: 5, quality: 4 etc") +} + +input ProductReviewRatingInput { + id: String! @doc(description: "Base 64 encoded rating id.") + value_id: String! @doc(description: "Base 64 encoded rating value id.") +} diff --git a/app/code/Magento/ReviewGraphQl/registration.php b/app/code/Magento/ReviewGraphQl/registration.php new file mode 100644 index 0000000000000..8fb6535902edf --- /dev/null +++ b/app/code/Magento/ReviewGraphQl/registration.php @@ -0,0 +1,9 @@ +customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->customerRepository = $objectManager->get(CustomerRepositoryInterface::class); + $this->reviewCollectionFactory = $objectManager->get(ReviewCollectionFactory::class); + $this->registry = $objectManager->get(Registry::class); + } + + /** + * Test adding a product review as guest and logged in customer + * + * @param string $customerName + * @param bool $isGuest + * + * @magentoApiDataFixture Magento/Review/_files/set_position_and_add_store_to_all_ratings.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @dataProvider customerDataProvider + */ + public function testCustomerAddProductReviews(string $customerName, bool $isGuest) + { + $productSku = 'simple_product'; + $query = $this->getQuery($productSku, $customerName); + $headers = []; + + if (!$isGuest) { + $headers = $this->getHeaderMap(); + } + + $response = $this->graphQlMutation($query, [], '', $headers); + + $expectedResult = [ + 'nickname' => $customerName, + 'summary' => 'Summary Test', + 'text' => 'Text Test', + 'average_rating' => 3.33, + 'ratings_breakdown' => [ + [ + 'name' => 'Price', + 'value' => 3 + ], [ + 'name' => 'Quality', + 'value' => 2 + ], [ + 'name' => 'Value', + 'value' => 5 + ] + ] + ]; + self::assertArrayHasKey('createProductReview', $response); + self::assertArrayHasKey('review', $response['createProductReview']); + self::assertEquals($expectedResult, $response['createProductReview']['review']); + } + + /** + * @magentoConfigFixture default_store catalog/review/allow_guest 0 + */ + public function testAddProductReviewGuestIsNotAllowed() + { + $productSku = 'simple_product'; + $customerName = 'John Doe'; + $query = $this->getQuery($productSku, $customerName); + self::expectExceptionMessage('Guest customers aren\'t allowed to add product reviews.'); + $this->graphQlMutation($query); + } + + /** + * Removing the recently added product reviews + */ + public function tearDown(): void + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + $productId = 1; + /** @var Collection $reviewsCollection */ + $reviewsCollection = $this->reviewCollectionFactory->create(); + $reviewsCollection->addEntityFilter(Review::ENTITY_PRODUCT_CODE, $productId); + /** @var Review $review */ + foreach ($reviewsCollection as $review) { + $review->delete(); + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + + parent::tearDown(); + } + + /** + * @return array + */ + public function customerDataProvider(): array + { + return [ + 'Guest Customer' => ['John Doe', true], + 'Logged In Customer' => ['John', false], + ]; + } + + /** + * @param string $username + * @param string $password + * + * @return array + * + * @throws AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * Get mutation query + * + * @param string $sku + * @param string $customerName + * + * @return string + */ + private function getQuery(string $sku, string $customerName): string + { + return << 'MTI=', + 'value' => "2" + ],[ + 'value_id' => 'MTM=', + 'value' => "3" + ],[ + 'value_id' => 'MTQ=', + 'value' => "4" + ],[ + 'value_id' => 'MTU=', + 'value' => "5" + ] + ] + ], [ + 'id' => 'MQ==', + 'name' => 'Quality', + 'values' => [ + [ + 'value_id' => 'MQ==', + 'value' => "1" + ],[ + 'value_id' => 'Mg==', + 'value' => "2" + ],[ + 'value_id' => 'Mw==', + 'value' => "3" + ],[ + 'value_id' => 'NA==', + 'value' => "4" + ],[ + 'value_id' => 'NQ==', + 'value' => "5" + ] + ] + ], [ + 'id' => 'Mg==', + 'name' => 'Value', + 'values' => [ + [ + 'value_id' => 'Ng==', + 'value' => "1" + ],[ + 'value_id' => 'Nw==', + 'value' => "2" + ],[ + 'value_id' => 'OA==', + 'value' => "3" + ],[ + 'value_id' => 'OQ==', + 'value' => "4" + ],[ + 'value_id' => 'MTA=', + 'value' => "5" + ] + ] + ] + ]; + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('productReviewRatingsMetadata', $response); + self::assertArrayHasKey('items', $response['productReviewRatingsMetadata']); + self::assertNotEmpty($response['productReviewRatingsMetadata']['items']); + self::assertEquals($expectedRatingItems, $response['productReviewRatingsMetadata']['items']); + } + + /** + * @magentoApiDataFixture Magento/Review/_files/different_reviews.php + */ + public function testProductReviewRatings() + { + $productSku = 'simple'; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product = $productRepository->get($productSku, false, null, true); + $summaryFactory = ObjectManager::getInstance()->get(SummaryFactory::class); + $storeId = ObjectManager::getInstance()->get(StoreManagerInterface::class)->getStore()->getId(); + $summary = $summaryFactory->create()->setStoreId($storeId)->load($product->getId()); + $query + = <<graphQlQuery($query); + self::assertArrayHasKey('products', $response); + self::assertArrayHasKey('items', $response['products']); + self::assertNotEmpty($response['products']['items']); + + $items = $response['products']['items']; + self::assertEquals($summary->getData('rating_summary'), $items[0]['rating_summary']); + self::assertEquals($summary->getData('reviews_count'), $items[0]['review_count']); + self::assertArrayHasKey('items', $items[0]['reviews']); + self::assertNotEmpty($items[0]['reviews']['items']); + } + + /** + * @magentoApiDataFixture Magento/Review/_files/customer_review_with_rating.php + */ + public function testCustomerReviewsAddedToProduct() + { + $query = << 'Nickname', + 'summary' => 'Review Summary', + 'text' => 'Review text', + 'average_rating' => 2, + 'ratings_breakdown' => [ + [ + 'name' => 'Quality', + 'value' => 2 + ],[ + 'name' => 'Value', + 'value' => 2 + ] + ] + ]; + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('customer', $response); + self::assertArrayHasKey('reviews', $response['customer']); + self::assertArrayHasKey('items', $response['customer']['reviews']); + self::assertNotEmpty($response['customer']['reviews']['items']); + self::assertEquals($expectedFirstItem, $response['customer']['reviews']['items'][0]); + } + + /** + * Removing the recently added product reviews + */ + public function tearDown(): void + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + $productId = 1; + /** @var Collection $reviewsCollection */ + $reviewsCollection = $this->reviewCollectionFactory->create(); + $reviewsCollection->addEntityFilter(Review::ENTITY_PRODUCT_CODE, $productId); + /** @var Review $review */ + foreach ($reviewsCollection as $review) { + $review->delete(); + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + + parent::tearDown(); + } + + /** + * @param string $username + * @param string $password + * + * @return array + * + * @throws AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + + return ['Authorization' => 'Bearer ' . $customerToken]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Review/_files/set_position_and_add_store_to_all_ratings.php b/dev/tests/integration/testsuite/Magento/Review/_files/set_position_and_add_store_to_all_ratings.php new file mode 100644 index 0000000000000..0c097f62101f8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/_files/set_position_and_add_store_to_all_ratings.php @@ -0,0 +1,31 @@ +loadArea(FrontNameResolver::AREA_CODE); + +$objectManager = Bootstrap::getObjectManager(); + +$storeId = $objectManager->get(StoreManagerInterface::class)->getStore()->getId(); + +/** @var RatingResourceModel $ratingResourceModel */ +$ratingResourceModel = $objectManager->create(RatingResourceModel::class); + +/** @var RatingCollection $ratingCollection */ +$ratingCollection = $objectManager->create(RatingCollection::class)->setOrder('rating_code', 'ASC'); +$position = 0; + +foreach ($ratingCollection as $rating) { + $rating->setStores([$storeId])->setPosition($position++); + $ratingResourceModel->save($rating); +} diff --git a/dev/tests/integration/testsuite/Magento/Review/_files/set_position_and_add_store_to_all_ratings_rollback.php b/dev/tests/integration/testsuite/Magento/Review/_files/set_position_and_add_store_to_all_ratings_rollback.php new file mode 100644 index 0000000000000..3a96a1be17a8b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/_files/set_position_and_add_store_to_all_ratings_rollback.php @@ -0,0 +1,29 @@ +loadArea(FrontNameResolver::AREA_CODE); +$objectManager = Bootstrap::getObjectManager(); + +$storeId = Bootstrap::getObjectManager()->get(StoreManagerInterface::class)->getStore()->getId(); + +/** @var RatingResourceModel $ratingResourceModel */ +$ratingResourceModel = $objectManager->create(RatingResourceModel::class); + +/** @var RatingCollection $ratingCollection */ +$ratingCollection = Bootstrap::getObjectManager()->create(RatingCollection::class); + +foreach ($ratingCollection as $rating) { + $rating->setStores([])->setPosition(0); + $ratingResourceModel->save($rating); +} From 51e2fbc741d8f6892432f8e9b83eec2c1737874b Mon Sep 17 00:00:00 2001 From: eduard13 Date: Sat, 9 May 2020 09:19:48 +0300 Subject: [PATCH 2/9] Small refactoring to re-implement the customer reviews --- .../AddRatingVotesToCustomerReviews.php | 55 ------------------- .../Service/GetReviewAverageRatingService.php | 32 ----------- .../ReviewGraphQl/Mapper/ReviewDataMapper.php | 7 ++- .../CustomerReviewsDataProvider.php | 37 ++++++------- .../ReviewRatingsDataProvider.php | 27 ++++++++- .../Resolver/Product/Review/AverageRating.php | 30 ++++++---- .../Product/Review/RatingBreakdown.php | 12 ++-- 7 files changed, 69 insertions(+), 131 deletions(-) delete mode 100644 app/code/Magento/Review/Model/Review/AddRatingVotesToCustomerReviews.php delete mode 100644 app/code/Magento/Review/Service/GetReviewAverageRatingService.php diff --git a/app/code/Magento/Review/Model/Review/AddRatingVotesToCustomerReviews.php b/app/code/Magento/Review/Model/Review/AddRatingVotesToCustomerReviews.php deleted file mode 100644 index c50790405a880..0000000000000 --- a/app/code/Magento/Review/Model/Review/AddRatingVotesToCustomerReviews.php +++ /dev/null @@ -1,55 +0,0 @@ -ratingOptionCollectionFactory = $ratingOptionCollectionFactory; - } - - /** - * Add rating votes to customer reviews - * - * @param Collection $collection - */ - public function execute(Collection $collection): void - { - $connection = $collection->getConnection(); - - foreach ($collection->getItems() as &$item) { - /** @var OptionVoteCollection $votesCollection */ - $votesCollection = $this->ratingOptionCollectionFactory->create(); - - $votesCollection->addFieldToFilter('main_table.review_id', $item->getData('review_id')); - $votesCollection->getSelect() - ->join( - ['rating' => $connection->getTableName('rating')], - 'rating.rating_id = main_table.rating_id', - ['rating_code'] - ); - $item->setRatingVotes($votesCollection); - } - } -} diff --git a/app/code/Magento/Review/Service/GetReviewAverageRatingService.php b/app/code/Magento/Review/Service/GetReviewAverageRatingService.php deleted file mode 100644 index 92175b717bcc8..0000000000000 --- a/app/code/Magento/Review/Service/GetReviewAverageRatingService.php +++ /dev/null @@ -1,32 +0,0 @@ -getData('value'); - } - - return $averageRating > 0 ? (float) number_format($averageRating / count($ratingVotes), 2) : 0; - } -} diff --git a/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php b/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php index 324775f61fa66..e5a05712e3018 100644 --- a/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php +++ b/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php @@ -18,11 +18,11 @@ class ReviewDataMapper /** * Mapping the review data * - * @param Review|Product $review + * @param Review $review * * @return array */ - public function map($review): array + public function map(Review $review): array { return [ 'summary' => $review->getData('title'), @@ -30,7 +30,8 @@ public function map($review): array 'nickname' => $review->getData('nickname'), 'created_at' => $review->getData('created_at'), 'rating_votes' => $review->getData('rating_votes'), - 'sku' => $review->getSku() + 'sku' => $review->getSku(), + 'model' => $review ]; } } diff --git a/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php b/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php index 643ab5954641b..f8dc5b1faead4 100644 --- a/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php +++ b/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php @@ -7,35 +7,26 @@ namespace Magento\ReviewGraphQl\Model\DataProvider; -use Magento\Review\Model\ResourceModel\Review\Product\Collection as ProductReviewsCollection; -use Magento\Review\Model\ResourceModel\Review\Product\CollectionFactory; -use Magento\Review\Model\Review\AddRatingVotesToCustomerReviews; - +use Magento\Review\Model\ResourceModel\Review\Collection as ReviewsCollection; +use Magento\Review\Model\ResourceModel\Review\CollectionFactory as ReviewsCollectionFactory; +use Magento\Review\Model\Review; /** * Provides customer reviews */ class CustomerReviewsDataProvider { /** - * @var CollectionFactory + * @var ReviewsCollectionFactory */ private $collectionFactory; /** - * @var AddRatingVotesToCustomerReviews - */ - private $addRatingVotesToCustomerReviews; - - /** - * @param CollectionFactory $collectionFactory - * @param AddRatingVotesToCustomerReviews $addRatingVotesToCustomerReviews + * @param ReviewsCollectionFactory $collectionFactory */ public function __construct( - CollectionFactory $collectionFactory, - AddRatingVotesToCustomerReviews $addRatingVotesToCustomerReviews + ReviewsCollectionFactory $collectionFactory ) { $this->collectionFactory = $collectionFactory; - $this->addRatingVotesToCustomerReviews = $addRatingVotesToCustomerReviews; } /** @@ -45,17 +36,23 @@ public function __construct( * @param int $currentPage * @param int $pageSize * - * @return ProductReviewsCollection + * @return ReviewsCollection */ - public function getData(int $customerId, int $currentPage, int $pageSize): ProductReviewsCollection + public function getData(int $customerId, int $currentPage, int $pageSize): ReviewsCollection { - /** @var ProductReviewsCollection $reviewsCollection */ + /** @var ReviewsCollection $reviewsCollection */ $reviewsCollection = $this->collectionFactory->create(); - $reviewsCollection->addCustomerFilter($customerId) + $reviewsCollection->addStatusFilter(Review::STATUS_APPROVED) + ->addCustomerFilter($customerId) ->setPageSize($pageSize) ->setCurPage($currentPage) ->setDateOrder(); - $this->addRatingVotesToCustomerReviews->execute($reviewsCollection); + $reviewsCollection->getSelect()->join( + ['cpe' => $reviewsCollection->getTable('catalog_product_entity')], + 'cpe.entity_id = main_table.entity_pk_value', + ['sku'] + ); + $reviewsCollection->addRateVotes(); return $reviewsCollection; } diff --git a/app/code/Magento/ReviewGraphQl/Model/DataProvider/ReviewRatingsDataProvider.php b/app/code/Magento/ReviewGraphQl/Model/DataProvider/ReviewRatingsDataProvider.php index a327f51c3e696..82e0f73b1c774 100644 --- a/app/code/Magento/ReviewGraphQl/Model/DataProvider/ReviewRatingsDataProvider.php +++ b/app/code/Magento/ReviewGraphQl/Model/DataProvider/ReviewRatingsDataProvider.php @@ -7,23 +7,44 @@ namespace Magento\ReviewGraphQl\Model\DataProvider; +use Magento\Review\Model\ResourceModel\Rating\Option\Vote\Collection as VoteCollection; +use Magento\Review\Model\ResourceModel\Rating\Option\Vote\CollectionFactory as VoteCollectionFactory; + /** * Provides rating votes */ class ReviewRatingsDataProvider { + /** + * @var VoteCollectionFactory + */ + private $voteCollectionFactory; + + /** + * @param VoteCollectionFactory $voteCollectionFactory + */ + public function __construct(VoteCollectionFactory $voteCollectionFactory) + { + $this->voteCollectionFactory = $voteCollectionFactory; + } + /** * Providing rating votes * - * @param array $ratingVotes + * @param int $reviewId * * @return array */ - public function getData(array $ratingVotes): array + public function getData(int $reviewId): array { + /** @var VoteCollection $ratingVotes */ + $ratingVotes = $this->voteCollectionFactory->create(); + $ratingVotes->setReviewFilter($reviewId); + $ratingVotes->addRatingInfo(); + $data = []; - foreach ($ratingVotes as $ratingVote) { + foreach ($ratingVotes->getItems() as $ratingVote) { $data[] = [ 'name' => $ratingVote->getData('rating_code'), 'value' => $ratingVote->getData('value') diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php index eb84856ba47d6..b33ea592425af 100644 --- a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php @@ -13,8 +13,8 @@ use Magento\Framework\GraphQl\Query\Resolver\Value; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Review\Model\ResourceModel\Rating\Option\Vote\Collection as VoteCollection; -use Magento\Review\Service\GetReviewAverageRatingService; +use Magento\Review\Model\RatingFactory; +use Magento\Review\Model\Review; /** * Review average rating resolver @@ -22,17 +22,17 @@ class AverageRating implements ResolverInterface { /** - * @var GetReviewAverageRatingService + * @var RatingFactory */ - private $getReviewAverageRatingService; + private $ratingFactory; /** - * @param GetReviewAverageRatingService $getReviewAverageRatingService + * @param RatingFactory $ratingFactory */ public function __construct( - GetReviewAverageRatingService $getReviewAverageRatingService + RatingFactory $ratingFactory ) { - $this->getReviewAverageRatingService = $getReviewAverageRatingService; + $this->ratingFactory = $ratingFactory; } /** @@ -57,13 +57,19 @@ public function resolve( array $value = null, array $args = null ) { - if (!isset($value['rating_votes'])) { - throw new GraphQlInputException(__('Value must contain "rating_votes" property.')); + if (!isset($value['model'])) { + throw new GraphQlInputException(__('Value must contain "model" property.')); } - /** @var VoteCollection $ratingVotes */ - $ratingVotes = $value['rating_votes']; + /** @var Review $review */ + $review = $value['model']; + $summary = $this->ratingFactory->create()->getReviewSummary($review->getId()); + $averageRating = $summary->getSum(); - return $this->getReviewAverageRatingService->execute($ratingVotes->getItems()); + if ($summary->getSum() > 0) { + $averageRating = (float) number_format($summary->getSum() / $summary->getCount() / 20, 2); + } + + return $averageRating; } } diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/RatingBreakdown.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/RatingBreakdown.php index 050afed45dbfb..a51bd0420dda9 100644 --- a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/RatingBreakdown.php +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/RatingBreakdown.php @@ -13,7 +13,7 @@ use Magento\Framework\GraphQl\Query\Resolver\Value; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Review\Model\ResourceModel\Rating\Option\Vote\Collection as VoteCollection; +use Magento\Review\Model\Review; use Magento\ReviewGraphQl\Model\DataProvider\ReviewRatingsDataProvider; /** @@ -57,13 +57,13 @@ public function resolve( array $value = null, array $args = null ) { - if (!isset($value['rating_votes'])) { - throw new GraphQlInputException(__('Value must contain "rating_votes" property.')); + if (!isset($value['model'])) { + throw new GraphQlInputException(__('Value must contain "model" property.')); } - /** @var VoteCollection $ratingVotes */ - $ratingVotes = $value['rating_votes']; + /** @var Review $review */ + $review = $value['model']; - return $this->reviewRatingsDataProvider->getData($ratingVotes->getItems()); + return $this->reviewRatingsDataProvider->getData((int) $review->getId()); } } From 1d75c53591e415a36c30b7155eae61c9be2a5661 Mon Sep 17 00:00:00 2001 From: eduard13 Date: Thu, 14 May 2020 13:50:18 +0300 Subject: [PATCH 3/9] Small improvements --- .../Model/DataProvider/CustomerReviewsDataProvider.php | 1 + app/code/Magento/ReviewGraphQl/composer.json | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php b/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php index f8dc5b1faead4..a1d7b4bb7d7cc 100644 --- a/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php +++ b/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php @@ -10,6 +10,7 @@ use Magento\Review\Model\ResourceModel\Review\Collection as ReviewsCollection; use Magento\Review\Model\ResourceModel\Review\CollectionFactory as ReviewsCollectionFactory; use Magento\Review\Model\Review; + /** * Provides customer reviews */ diff --git a/app/code/Magento/ReviewGraphQl/composer.json b/app/code/Magento/ReviewGraphQl/composer.json index 6e8b6f0472e45..819ddefd76213 100644 --- a/app/code/Magento/ReviewGraphQl/composer.json +++ b/app/code/Magento/ReviewGraphQl/composer.json @@ -3,16 +3,15 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.1.3||~7.2.0||~7.3.0", + "php": "~7.3.0||~7.4.0", "magento/module-catalog": "*", "magento/module-review": "*", - "magento/framework": "*", "magento/module-store": "*", - "magento/module-graph-ql": "*" + "magento/framework": "*" }, "suggest": { - "magento/module-graph-ql-cache": "*", - "magento/module-store-graph-ql": "*" + "magento/module-graph-ql": "*", + "magento/module-graph-ql-cache": "*" }, "license": [ "OSL-3.0", From 017da272abe3d461994c937f4c8196d50b8ef41c Mon Sep 17 00:00:00 2001 From: eduard13 Date: Mon, 22 Jun 2020 09:33:32 +0300 Subject: [PATCH 4/9] Updating schema description --- .../Magento/ReviewGraphQl/etc/schema.graphqls | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/ReviewGraphQl/etc/schema.graphqls b/app/code/Magento/ReviewGraphQl/etc/schema.graphqls index e9b99a48bb99a..14b4fc60e8b09 100644 --- a/app/code/Magento/ReviewGraphQl/etc/schema.graphqls +++ b/app/code/Magento/ReviewGraphQl/etc/schema.graphqls @@ -17,31 +17,31 @@ type ProductReviews { type ProductReview @doc(description: "Details of a product review") { product: ProductInterface! @doc(description: "Contains details about the reviewed product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product") - summary: String! @doc(description: "The review summary (a.k.a title") + summary: String! @doc(description: "The summary (title) of the review") text: String! @doc(description: "The review text.") - nickname: String! @doc(description: "The customer's nickname. Defaults to customer name if logged in.") + nickname: String! @doc(description: "The customer's nickname. Defaults to the customer name, if logged in") created_at: String! @doc(description: "Date indicating when the review was created.") average_rating: Float! @doc(description: "The average rating for product review.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Product\\Review\\AverageRating") - ratings_breakdown: [ProductReviewRating!]! @doc(description: "An array of ratings by rating category. For example quality, price.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Product\\Review\\RatingBreakdown") + ratings_breakdown: [ProductReviewRating!]! @doc(description: "An array of ratings by rating category, such as quality, price, and value") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Product\\Review\\RatingBreakdown") } type ProductReviewRating { - name: String! @doc(description: "The review rating name for example quality, price.") - value: String! @doc(description: "The rating value given by customer. Possible values by default: 1 to 5.") + name: String! @doc(description: "The label assigned to an aspect of a product that is being rated, such as quality or price") + value: String! @doc(description: "The rating value given by customer. By default, possible values range from 1 to 5.") } type Query { - productReviewRatingsMetadata: ProductReviewRatingsMetadata! @doc(description: "Metadata required by clients to render ratings & reviews section.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\ProductReviewRatingsMetadata") + productReviewRatingsMetadata: ProductReviewRatingsMetadata! @doc(description: "Retrieves metadata required by clients to render the Reviews section.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\ProductReviewRatingsMetadata") } type ProductReviewRatingsMetadata { - items: [ProductReviewRatingMetadata!]! @doc(description: "List of product reviews sorted based on position") + items: [ProductReviewRatingMetadata!]! @doc(description: "List of product reviews sorted by position") } type ProductReviewRatingMetadata { - id: String! @doc(description: "Base 64 encoded rating id.") - name: String! @doc(description: "The review rating name for example quality, price") - values: [ProductReviewRatingValueMetadata!]! @doc(description: "List of product review ratings sorted based on position.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\ProductReviewRatingValueMetadata") + id: String! @doc(description: "Base64 encoded rating ID.") + name: String! @doc(description: "The label assigned to an aspect of a product that is being rated, such as quality or price") + values: [ProductReviewRatingValueMetadata!]! @doc(description: "List of product review ratings sorted by position.") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\ProductReviewRatingValueMetadata") } type ProductReviewRatingValueMetadata { @@ -53,26 +53,26 @@ type Customer { reviews( pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once."), currentPage: Int = 1 @doc(description: "Specifies which page of results to return."), - ): ProductReviews! @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Customer\\Reviews") + ): ProductReviews! @doc(description: "Contains the customer's product reviews") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\Customer\\Reviews") } type Mutation { - createProductReview(input: CreateProductReviewInput!): CreateProductReviewOutput! @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\CreateProductReview") + createProductReview(input: CreateProductReviewInput!): CreateProductReviewOutput! @doc(description: "Creates a product review for the specified SKU") @resolver(class: "Magento\\ReviewGraphQl\\Model\\Resolver\\CreateProductReview") } type CreateProductReviewOutput { - review: ProductReview! + review: ProductReview! @doc(description: "Contains the completed product review") } input CreateProductReviewInput { - sku: String! @doc(description: "The SKU of the product that the review is assigned") - nickname: String! @doc(description: "The customer's nickname. Defaults to customer name if logged in.") - summary: String! @doc(description: "The review summary (a.k.a title") + sku: String! @doc(description: "The SKU of the reviewed product") + nickname: String! @doc(description: "The customer's nickname. Defaults to the customer name, if logged in") + summary: String! @doc(description: "The summary (title) of the review") text: String! @doc(description: "The review text.") ratings: [ProductReviewRatingInput!]! @doc(description: "Ratings details by category. e.g price: 5, quality: 4 etc") } input ProductReviewRatingInput { - id: String! @doc(description: "Base 64 encoded rating id.") + id: String! @doc(description: "Base64 encoded rating ID.") value_id: String! @doc(description: "Base 64 encoded rating value id.") } From a020625517c036c3f671eccdb88b2ec772ce188c Mon Sep 17 00:00:00 2001 From: eduard13 Date: Wed, 24 Jun 2020 22:23:02 +0300 Subject: [PATCH 5/9] Adding reviews config checking --- .../Magento/Review/Model/Review/Config.php | 46 +++++++++++++++++++ .../ReviewGraphQl/Mapper/ReviewDataMapper.php | 1 - .../Model/Resolver/CreateProductReview.php | 15 +++++- .../Model/Resolver/Product/RatingSummary.php | 15 +++++- .../Model/Resolver/Product/ReviewCount.php | 14 +++++- .../Model/Resolver/Product/Reviews.php | 15 +++++- .../Resolver/ProductReviewRatingsMetadata.php | 14 +++++- 7 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 app/code/Magento/Review/Model/Review/Config.php diff --git a/app/code/Magento/Review/Model/Review/Config.php b/app/code/Magento/Review/Model/Review/Config.php new file mode 100644 index 0000000000000..8fa32f0a7e340 --- /dev/null +++ b/app/code/Magento/Review/Model/Review/Config.php @@ -0,0 +1,46 @@ +scopeConfig = $scopeConfig; + } + + /** + * Check whether the reviews are enabled or not + * + * @return bool + */ + public function isEnabled(): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_PATH_WISHLIST_ACTIVE, + ScopeInterface::SCOPE_STORES + ); + } +} diff --git a/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php b/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php index e5a05712e3018..6a06fbfc4102c 100644 --- a/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php +++ b/app/code/Magento/ReviewGraphQl/Mapper/ReviewDataMapper.php @@ -29,7 +29,6 @@ public function map(Review $review): array 'text' => $review->getData('detail'), 'nickname' => $review->getData('nickname'), 'created_at' => $review->getData('created_at'), - 'rating_votes' => $review->getData('rating_votes'), 'sku' => $review->getSku(), 'model' => $review ]; diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/CreateProductReview.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/CreateProductReview.php index 6db31f3a50f33..9b0171c3b700a 100644 --- a/app/code/Magento/ReviewGraphQl/Model/Resolver/CreateProductReview.php +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/CreateProductReview.php @@ -15,6 +15,7 @@ use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Review\Helper\Data as ReviewHelper; +use Magento\Review\Model\Review\Config as ReviewsConfig; use Magento\ReviewGraphQl\Mapper\ReviewDataMapper; use Magento\ReviewGraphQl\Model\Review\AddReviewToProduct; use Magento\Store\Api\Data\StoreInterface; @@ -39,20 +40,28 @@ class CreateProductReview implements ResolverInterface */ private $reviewDataMapper; + /** + * @var ReviewsConfig + */ + private $reviewsConfig; + /** * @param AddReviewToProduct $addReviewToProduct * @param ReviewDataMapper $reviewDataMapper * @param ReviewHelper $reviewHelper + * @param ReviewsConfig $reviewsConfig */ public function __construct( AddReviewToProduct $addReviewToProduct, ReviewDataMapper $reviewDataMapper, - ReviewHelper $reviewHelper + ReviewHelper $reviewHelper, + ReviewsConfig $reviewsConfig ) { $this->addReviewToProduct = $addReviewToProduct; $this->reviewDataMapper = $reviewDataMapper; $this->reviewHelper = $reviewHelper; + $this->reviewsConfig = $reviewsConfig; } /** @@ -78,6 +87,10 @@ public function resolve( array $value = null, array $args = null ) { + if (false === $this->reviewsConfig->isEnabled()) { + throw new GraphQlAuthorizationException(__('Creating product reviews are not currently available.')); + } + $input = $args['input']; $customerId = null; diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/RatingSummary.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/RatingSummary.php index 56c7e7b5912f2..eed5034c59daa 100644 --- a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/RatingSummary.php +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/RatingSummary.php @@ -14,6 +14,7 @@ use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Review\Model\Review\Config as ReviewsConfig; use Magento\Review\Model\Review\SummaryFactory; use Magento\Store\Api\Data\StoreInterface; @@ -27,13 +28,21 @@ class RatingSummary implements ResolverInterface */ private $summaryFactory; + /** + * @var ReviewsConfig + */ + private $reviewsConfig; + /** * @param SummaryFactory $summaryFactory + * @param ReviewsConfig $reviewsConfig */ public function __construct( - SummaryFactory $summaryFactory + SummaryFactory $summaryFactory, + ReviewsConfig $reviewsConfig ) { $this->summaryFactory = $summaryFactory; + $this->reviewsConfig = $reviewsConfig; } /** @@ -58,6 +67,10 @@ public function resolve( array $value = null, array $args = null ): float { + if (false === $this->reviewsConfig->isEnabled()) { + return 0; + } + if (!isset($value['model'])) { throw new GraphQlInputException(__('Value must contain "model" property.')); } diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php index 5e9fa490fcd8f..dfa62adf0266e 100644 --- a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php @@ -15,6 +15,7 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Review\Model\Review; +use Magento\Review\Model\Review\Config as ReviewsConfig; /** * Product total review count @@ -26,12 +27,19 @@ class ReviewCount implements ResolverInterface */ private $review; + /** + * @var ReviewsConfig + */ + private $reviewsConfig; + /** * @param Review $review + * @param ReviewsConfig $reviewsConfig */ - public function __construct(Review $review) + public function __construct(Review $review, ReviewsConfig $reviewsConfig) { $this->review = $review; + $this->reviewsConfig = $reviewsConfig; } /** @@ -56,6 +64,10 @@ public function resolve( array $value = null, array $args = null ) { + if (false === $this->reviewsConfig->isEnabled()) { + return 0; + } + if (!isset($value['model'])) { throw new GraphQlInputException(__('Value must contain "model" property.')); } diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php index f414e189d8b1f..72eea5e6b3bd2 100644 --- a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php @@ -14,6 +14,7 @@ use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Review\Model\Review\Config as ReviewsConfig; use Magento\ReviewGraphQl\Model\DataProvider\AggregatedReviewsDataProvider; use Magento\ReviewGraphQl\Model\DataProvider\ProductReviewsDataProvider; @@ -32,16 +33,24 @@ class Reviews implements ResolverInterface */ private $aggregatedReviewsDataProvider; + /** + * @var ReviewsConfig + */ + private $reviewsConfig; + /** * @param ProductReviewsDataProvider $productReviewsDataProvider * @param AggregatedReviewsDataProvider $aggregatedReviewsDataProvider + * @param ReviewsConfig $reviewsConfig */ public function __construct( ProductReviewsDataProvider $productReviewsDataProvider, - AggregatedReviewsDataProvider $aggregatedReviewsDataProvider + AggregatedReviewsDataProvider $aggregatedReviewsDataProvider, + ReviewsConfig $reviewsConfig ) { $this->productReviewsDataProvider = $productReviewsDataProvider; $this->aggregatedReviewsDataProvider = $aggregatedReviewsDataProvider; + $this->reviewsConfig = $reviewsConfig; } /** @@ -66,6 +75,10 @@ public function resolve( array $value = null, array $args = null ) { + if (false === $this->reviewsConfig->isEnabled()) { + return ['items' => []]; + } + if (!isset($value['model'])) { throw new GraphQlInputException(__('Value must contain "model" property.')); } diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingsMetadata.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingsMetadata.php index 075666ef9a0ad..2cf536255baf7 100644 --- a/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingsMetadata.php +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/ProductReviewRatingsMetadata.php @@ -15,6 +15,7 @@ use Magento\Review\Model\ResourceModel\Rating\Collection as RatingCollection; use Magento\Review\Model\ResourceModel\Rating\CollectionFactory; use Magento\Review\Model\Review; +use Magento\Review\Model\Review\Config as ReviewsConfig; use Magento\Store\Api\Data\StoreInterface; /** @@ -27,12 +28,19 @@ class ProductReviewRatingsMetadata implements ResolverInterface */ private $ratingCollectionFactory; + /** + * @var ReviewsConfig + */ + private $reviewsConfig; + /** * @param CollectionFactory $ratingCollectionFactory + * @param ReviewsConfig $reviewsConfig */ - public function __construct(CollectionFactory $ratingCollectionFactory) + public function __construct(CollectionFactory $ratingCollectionFactory, ReviewsConfig $reviewsConfig) { $this->ratingCollectionFactory = $ratingCollectionFactory; + $this->reviewsConfig = $reviewsConfig; } /** @@ -55,6 +63,10 @@ public function resolve( array $value = null, array $args = null ) { + if (false === $this->reviewsConfig->isEnabled()) { + return ['items' => []]; + } + $items = []; /** @var StoreInterface $store */ $store = $context->getExtensionAttributes()->getStore(); From 0111b2d5956a791ef1cc4a63a95299a8f98b7899 Mon Sep 17 00:00:00 2001 From: eduard13 Date: Wed, 24 Jun 2020 23:05:05 +0300 Subject: [PATCH 6/9] Adding return types --- .../Magento/GraphQl/Review/CreateProductReviewsTest.php | 2 +- .../testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/CreateProductReviewsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/CreateProductReviewsTest.php index 0e4560b6af77a..77d2b5121e747 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/CreateProductReviewsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/CreateProductReviewsTest.php @@ -45,7 +45,7 @@ class CreateProductReviewsTest extends GraphQlAbstract /** * @inheritdoc */ - protected function setUp() + protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php index 8d0b462a4d5a5..3d5655b912914 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php @@ -43,7 +43,7 @@ class GetProductReviewsTest extends GraphQlAbstract /** * @inheritdoc */ - protected function setUp() + protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); From f168ca6067aee1caf741e9e124802a8d97e87205 Mon Sep 17 00:00:00 2001 From: eduard13 Date: Thu, 25 Jun 2020 15:31:51 +0300 Subject: [PATCH 7/9] Small improvements --- app/code/Magento/Review/Model/Review/Config.php | 4 ++-- .../Model/Resolver/Product/Review/AverageRating.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Review/Model/Review/Config.php b/app/code/Magento/Review/Model/Review/Config.php index 8fa32f0a7e340..a3082503b1391 100644 --- a/app/code/Magento/Review/Model/Review/Config.php +++ b/app/code/Magento/Review/Model/Review/Config.php @@ -15,7 +15,7 @@ */ class Config { - const XML_PATH_WISHLIST_ACTIVE = 'catalog/review/active'; + const XML_PATH_REVIEW_ACTIVE = 'catalog/review/active'; /** * @var ScopeConfigInterface @@ -39,7 +39,7 @@ public function __construct( public function isEnabled(): bool { return $this->scopeConfig->isSetFlag( - self::XML_PATH_WISHLIST_ACTIVE, + self::XML_PATH_REVIEW_ACTIVE, ScopeInterface::SCOPE_STORES ); } diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php index b33ea592425af..385d273d5368c 100644 --- a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php @@ -64,9 +64,9 @@ public function resolve( /** @var Review $review */ $review = $value['model']; $summary = $this->ratingFactory->create()->getReviewSummary($review->getId()); - $averageRating = $summary->getSum(); + $averageRating = $summary->getSum() ?: 0; - if ($summary->getSum() > 0) { + if ($averageRating > 0) { $averageRating = (float) number_format($summary->getSum() / $summary->getCount() / 20, 2); } From b974b487d21ed32795cb1d244ebf620f31663085 Mon Sep 17 00:00:00 2001 From: eduard13 Date: Tue, 7 Jul 2020 09:41:39 +0300 Subject: [PATCH 8/9] Adding rollback fixtures and providing small adjustments --- .../DataProvider/CustomerReviewsDataProvider.php | 2 +- .../Model/Resolver/Product/Review/AverageRating.php | 5 ++++- .../_files/customer_review_with_rating_rollback.php | 11 +++++++++++ .../Review/_files/different_reviews_rollback.php | 10 ++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Review/_files/customer_review_with_rating_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Review/_files/different_reviews_rollback.php diff --git a/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php b/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php index a1d7b4bb7d7cc..42adc8009c010 100644 --- a/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php +++ b/app/code/Magento/ReviewGraphQl/Model/DataProvider/CustomerReviewsDataProvider.php @@ -43,7 +43,7 @@ public function getData(int $customerId, int $currentPage, int $pageSize): Revie { /** @var ReviewsCollection $reviewsCollection */ $reviewsCollection = $this->collectionFactory->create(); - $reviewsCollection->addStatusFilter(Review::STATUS_APPROVED) + $reviewsCollection ->addCustomerFilter($customerId) ->setPageSize($pageSize) ->setCurPage($currentPage) diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php index 385d273d5368c..2e0d428b47873 100644 --- a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php +++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Review/AverageRating.php @@ -67,7 +67,10 @@ public function resolve( $averageRating = $summary->getSum() ?: 0; if ($averageRating > 0) { - $averageRating = (float) number_format($summary->getSum() / $summary->getCount() / 20, 2); + $averageRating = (float) number_format( + (int) $summary->getSum() / (int) $summary->getCount(), + 2 + ); } return $averageRating; diff --git a/dev/tests/integration/testsuite/Magento/Review/_files/customer_review_with_rating_rollback.php b/dev/tests/integration/testsuite/Magento/Review/_files/customer_review_with_rating_rollback.php new file mode 100644 index 0000000000000..0931d881a6fdc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/_files/customer_review_with_rating_rollback.php @@ -0,0 +1,11 @@ +requireDataFixture('Magento/Customer/_files/customer_rollback.php'); +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Review/_files/different_reviews_rollback.php b/dev/tests/integration/testsuite/Magento/Review/_files/different_reviews_rollback.php new file mode 100644 index 0000000000000..328c1e229da5c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/_files/different_reviews_rollback.php @@ -0,0 +1,10 @@ +requireDataFixture('Magento/Catalog/_files/product_simple_rollback.php'); From de8598c313b74b3f0bde8374a3d52d83022c2f2c Mon Sep 17 00:00:00 2001 From: eduard13 Date: Tue, 7 Jul 2020 18:57:27 +0300 Subject: [PATCH 9/9] Updating WebAPI tests --- .../Magento/GraphQl/Review/CreateProductReviewsTest.php | 2 +- .../testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/CreateProductReviewsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/CreateProductReviewsTest.php index 77d2b5121e747..f9df1dac5df34 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/CreateProductReviewsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/CreateProductReviewsTest.php @@ -82,7 +82,7 @@ public function testCustomerAddProductReviews(string $customerName, bool $isGues 'nickname' => $customerName, 'summary' => 'Summary Test', 'text' => 'Text Test', - 'average_rating' => 3.33, + 'average_rating' => 66.67, 'ratings_breakdown' => [ [ 'name' => 'Price', diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php index 3d5655b912914..f09a1827961f0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Review/GetProductReviewsTest.php @@ -226,7 +226,7 @@ public function testCustomerReviewsAddedToProduct() 'nickname' => 'Nickname', 'summary' => 'Review Summary', 'text' => 'Review text', - 'average_rating' => 2, + 'average_rating' => 40, 'ratings_breakdown' => [ [ 'name' => 'Quality',