Skip to content

Commit

Permalink
Handle feedback change approach to get star rating
Browse files Browse the repository at this point in the history
  • Loading branch information
ndkhanh-axonivy committed Jul 9, 2024
1 parent 68fec7e commit 84b9bf1
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 198 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.axonivy.market.assembler.FeedbackModelAssembler;
import com.axonivy.market.entity.Feedback;
import com.axonivy.market.model.FeedbackModel;
import com.axonivy.market.model.ProductRating;
import com.axonivy.market.service.FeedbackService;
import com.axonivy.market.service.JwtService;
import io.jsonwebtoken.Claims;
Expand All @@ -21,6 +22,7 @@
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;

import static com.axonivy.market.constants.RequestMappingConstants.FEEDBACK;

Expand Down Expand Up @@ -93,6 +95,12 @@ public ResponseEntity<Void> createFeedback(@RequestBody @Valid Feedback feedback
return ResponseEntity.created(location).build();
}

@Operation(summary = "Find rating information of product by id")
@GetMapping("/product/{productId}/rating")
public ResponseEntity<List<ProductRating>> getProductRating(@PathVariable String productId) {
return ResponseEntity.ok(service.getProductRatingById(productId));
}

@SuppressWarnings("unchecked")
private ResponseEntity<PagedModel<FeedbackModel>> generateEmptyPagedModel() {
var emptyPagedModel = (PagedModel<FeedbackModel>) pagedResourcesAssembler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,4 @@ private ResponseEntity<PagedModel<ProductModel>> generateEmptyPagedModel() {
.toEmptyModel(Page.empty(), ProductModel.class);
return new ResponseEntity<>(emptyPagedModel, HttpStatus.OK);
}

@Operation(summary = "Find rating information of product by id")
@GetMapping("{productId}/rating")
public ResponseEntity<List<ProductRating>> getProductRating(@PathVariable String productId) {
List<ProductRating> ratings = service.getProductRatingById(productId);
return ResponseEntity.ok(ratings);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public interface FeedbackRepository extends MongoRepository<Feedback, String> {
@Query("{ 'productId': ?0 }")
Page<Feedback> searchByProductId(String productId, Pageable pageable);

@Query("{ 'productId': ?0 }")
List<Feedback> getAllByProductId(String productId);

@Query("{ $and: [ { 'userId': ?0 }, { 'productId': ?1 } ] }")
List<Feedback> searchByProductIdAndUserId(String userId, String productId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import com.axonivy.market.entity.Feedback;
import com.axonivy.market.exceptions.model.NotFoundException;
import com.axonivy.market.model.ProductRating;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;

public interface FeedbackService {
Page<Feedback> findFeedbacks(String productId, Pageable pageable) throws NotFoundException;
Feedback findFeedback(String id) throws NotFoundException;
Feedback findFeedbackByUserIdAndProductId(String userId, String productId) throws NotFoundException;
Feedback upsertFeedback(Feedback feedback) throws NotFoundException;
List<ProductRating> getProductRatingById(String productId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@ public interface ProductService {
Page<Product> findProducts(String type, String keyword, String language, Pageable pageable);

boolean syncLatestDataFromMarketRepo();
List<ProductRating> getProductRatingById(String productId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.axonivy.market.entity.Product;
import com.axonivy.market.enums.ErrorCode;
import com.axonivy.market.exceptions.model.NotFoundException;
import com.axonivy.market.model.ProductRating;
import com.axonivy.market.repository.FeedbackRepository;
import com.axonivy.market.repository.ProductRepository;
import com.axonivy.market.repository.UserRepository;
Expand All @@ -14,7 +15,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class FeedbackServiceImpl implements FeedbackService {
Expand Down Expand Up @@ -56,95 +58,61 @@ public Feedback findFeedbackByUserIdAndProductId(String userId, String productId
@Transactional
@Override
public Feedback upsertFeedback(Feedback feedback) throws NotFoundException {
userRepository.findById(feedback.getUserId())
userRepository.findById(feedback.getUserId())
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND,"Not found user with id: " + feedback.getUserId()));
Product product = productRepository.findById(feedback.getProductId())
.orElseThrow(() -> new NotFoundException(ErrorCode.PRODUCT_NOT_FOUND, "Not found product with id: " + feedback.getProductId()));

List<Feedback> existingFeedbacks = feedbackRepository.searchByProductIdAndUserId(feedback.getUserId(), feedback.getProductId());

if (existingFeedbacks.isEmpty()) {
incrementStarCount(product, feedback.getRating());
productRepository.save(product);
return feedbackRepository.save(feedback);
} else {
Feedback existingFeedback = existingFeedbacks.get(0);
if (!existingFeedback.getRating().equals(feedback.getRating())) {
updateStarCount(product, existingFeedback.getRating(), feedback.getRating());
}
existingFeedback.setRating(feedback.getRating());
existingFeedback.setContent(feedback.getContent());
productRepository.save(product);
return feedbackRepository.save(existingFeedback);
}
}

private void validateProductExists(String productId) throws NotFoundException {
productRepository.findById(productId)
.orElseThrow(() -> new NotFoundException(ErrorCode.PRODUCT_NOT_FOUND, "Not found product with id: " + productId));
}

private void incrementStarCount(Product product, int rating) {
switch (rating) {
case 1:
product.setOneStarCount(getSafeCount(product.getOneStarCount()) + 1);
break;
case 2:
product.setTwoStarCount(getSafeCount(product.getTwoStarCount()) + 1);
break;
case 3:
product.setThreeStarCount(getSafeCount(product.getThreeStarCount()) + 1);
break;
case 4:
product.setFourStarCount(getSafeCount(product.getFourStarCount()) + 1);
break;
case 5:
product.setFiveStarCount(getSafeCount(product.getFiveStarCount()) + 1);
break;
default:
throw new IllegalArgumentException("Invalid rating: " + rating);
@Override
public List<ProductRating> getProductRatingById(String productId) {
List<Feedback> feedbacks = this.feedbackRepository.getAllByProductId(productId);
if (feedbacks.isEmpty()) {
return Arrays.asList(
new ProductRating(1, 0, 0),
new ProductRating(2, 0, 0),
new ProductRating(3, 0, 0),
new ProductRating(4, 0, 0),
new ProductRating(5, 0, 0)
);
}
}

private void decrementStarCount(Product product, int rating) {
switch (rating) {
case 1:
if (getSafeCount(product.getOneStarCount()) > 0) {
product.setOneStarCount(product.getOneStarCount() - 1);
}
break;
case 2:
if (getSafeCount(product.getTwoStarCount()) > 0) {
product.setTwoStarCount(product.getTwoStarCount() - 1);
}
break;
case 3:
if (getSafeCount(product.getThreeStarCount()) > 0) {
product.setThreeStarCount(product.getThreeStarCount() - 1);
}
break;
case 4:
if (getSafeCount(product.getFourStarCount()) > 0) {
product.setFourStarCount(product.getFourStarCount() - 1);
}
break;
case 5:
if (getSafeCount(product.getFiveStarCount()) > 0) {
product.setFiveStarCount(product.getFiveStarCount() - 1);
}
break;
default:
throw new IllegalArgumentException("Invalid rating: " + rating);
}
}
int totalFeedbacks = feedbacks.size();

Map<Integer, Long> ratingCountMap = new HashMap<>();
for (int i = 1; i <= 5; i++) {
ratingCountMap.put(i, 0L);
}

private void updateStarCount(Product product, int oldRating, int newRating) {
decrementStarCount(product, oldRating);
incrementStarCount(product, newRating);
ratingCountMap.putAll(feedbacks.stream()
.collect(Collectors.groupingBy(Feedback::getRating, Collectors.counting())));

return ratingCountMap.entrySet().stream()
.map(entry -> {
ProductRating productRating = new ProductRating();
productRating.setStarRating(entry.getKey());
productRating.setCommentNumber(Math.toIntExact(entry.getValue()));
productRating.setPercent((int) ((entry.getValue() * 100) / totalFeedbacks));
return productRating;
})
.collect(Collectors.toList());
}

private int getSafeCount(Integer count) {
return count == null ? 0 : count;
private void validateProductExists(String productId) throws NotFoundException {
productRepository.findById(productId)
.orElseThrow(() -> new NotFoundException(ErrorCode.PRODUCT_NOT_FOUND, "Not found product with id: " + productId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,65 +106,6 @@ public boolean syncLatestDataFromMarketRepo() {
return isAlreadyUpToDate;
}

@Override
public List<ProductRating> getProductRatingById(String productId) {
Product existingProduct = productRepository.findById(productId)
.orElseThrow(() -> new NotFoundException(ErrorCode.PRODUCT_NOT_FOUND, "Not found product with id: " + productId));
return mapToListProductRating(existingProduct);
}

private List<ProductRating> mapToListProductRating(Product product) {
int oneStarCount = getSafeCount(product.getOneStarCount());
int twoStarCount = getSafeCount(product.getTwoStarCount());
int threeStarCount = getSafeCount(product.getThreeStarCount());
int fourStarCount = getSafeCount(product.getFourStarCount());
int fiveStarCount = getSafeCount(product.getFiveStarCount());

int totalComments = oneStarCount + twoStarCount + threeStarCount + fourStarCount + fiveStarCount;

List<ProductRating> productRatings = new ArrayList<>();

productRatings.add(new ProductRating(
1,
oneStarCount,
calculatePercentage(oneStarCount, totalComments)
));

productRatings.add(new ProductRating(
2,
twoStarCount,
calculatePercentage(twoStarCount, totalComments)
));

productRatings.add(new ProductRating(
3,
threeStarCount,
calculatePercentage(threeStarCount, totalComments)
));

productRatings.add(new ProductRating(
4,
fourStarCount,
calculatePercentage(fourStarCount, totalComments)
));

productRatings.add(new ProductRating(
5,
fiveStarCount,
calculatePercentage(fiveStarCount, totalComments)
));

return productRatings;
}

private int getSafeCount(Integer count) {
return count == null ? 0 : count;
}

private int calculatePercentage(int starCount, int totalComments) {
return totalComments == 0 ? 0 : (int) ((starCount / (double) totalComments) * 100);
}

private void syncRepoMetaDataStatus() {
if (lastGHCommit == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,6 @@ void testSyncProducts() {
assertEquals(ErrorCode.SUCCESSFUL.getCode(), response.getBody().getHelpCode());
}

@Test
void testGetProductRating() {
List<ProductRating> productRatingMocks = List.of(createProductRatingMock());

when(service.getProductRatingById(PRODUCT_ID_SAMPLE)).thenReturn(productRatingMocks);
var result = productController.getProductRating(PRODUCT_ID_SAMPLE);

assertEquals(HttpStatus.OK, result.getStatusCode());
assertTrue(result.hasBody());
assertEquals(1, result.getBody().size());
assertEquals(productRatingMocks, result.getBody());
verify(service).getProductRatingById(PRODUCT_ID_SAMPLE);
}

private Product createProductMock() {
Product mockProduct = new Product();
mockProduct.setId("amazon-comprehend");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,56 +249,6 @@ void testSearchProducts() {
verify(productRepository).searchByNameOrShortDescriptionRegex(keyword, langague, simplePageable);
}

@Test
void testGetProductRatingById() {
when(productRepository.findById(SAMPLE_PRODUCT_ID)).thenReturn(java.util.Optional.of(createProductMock()));
// Executes
List<ProductRating> result = productService.getProductRatingById(SAMPLE_PRODUCT_ID);

// Assertions
assertEquals(5, result.size());
assertEquals(1, result.get(0).getStarRating());
assertEquals(SAMPLE_PRODUCT_ONE_STAR_COUNT, result.get(0).getCommentNumber());
assertEquals(calculatePercentage(SAMPLE_PRODUCT_ONE_STAR_COUNT), result.get(0).getPercent());

assertEquals(2, result.get(1).getStarRating());
assertEquals(SAMPLE_PRODUCT_TWO_STAR_COUNT, result.get(1).getCommentNumber());
assertEquals(calculatePercentage(SAMPLE_PRODUCT_TWO_STAR_COUNT), result.get(1).getPercent());

assertEquals(3, result.get(2).getStarRating());
assertEquals(SAMPLE_PRODUCT_THREE_STAR_COUNT, result.get(2).getCommentNumber());
assertEquals(calculatePercentage(SAMPLE_PRODUCT_THREE_STAR_COUNT), result.get(2).getPercent());

assertEquals(4, result.get(3).getStarRating());
assertEquals(SAMPLE_PRODUCT_FOUR_STAR_COUNT, result.get(3).getCommentNumber());
assertEquals(calculatePercentage(SAMPLE_PRODUCT_FOUR_STAR_COUNT), result.get(3).getPercent());

assertEquals(5, result.get(4).getStarRating());
assertEquals(SAMPLE_PRODUCT_FIVE_STAR_COUNT, result.get(4).getCommentNumber());
assertEquals(calculatePercentage(SAMPLE_PRODUCT_FIVE_STAR_COUNT), result.get(4).getPercent());
}

@Test
void testGetProductRatingById_ProductNotFound() {
when(productRepository.findById(any())).thenReturn(java.util.Optional.empty());

try {
productService.getProductRatingById("non-existent-product-id");
} catch (NotFoundException e) {
assertEquals("PRODUCT_NOT_FOUND-Not found product with id: non-existent-product-id", e.getMessage());
}
}

private int calculatePercentage(int starCount) {
int totalComments = SAMPLE_PRODUCT_ONE_STAR_COUNT +
SAMPLE_PRODUCT_TWO_STAR_COUNT +
SAMPLE_PRODUCT_THREE_STAR_COUNT +
SAMPLE_PRODUCT_FOUR_STAR_COUNT +
SAMPLE_PRODUCT_FIVE_STAR_COUNT;

return totalComments == 0 ? 0 : (int) ((starCount / (double) totalComments) * 100);
}

private Page<Product> createPageProductsMock() {
var mockProducts = new ArrayList<Product>();
MultilingualismValue name = new MultilingualismValue();
Expand Down

0 comments on commit 84b9bf1

Please sign in to comment.