From 0fad2319e6b815fc92b106bcd95a57524473195b Mon Sep 17 00:00:00 2001 From: Oleg Zhuk Date: Fri, 20 Sep 2024 12:49:35 +0200 Subject: [PATCH 1/4] VCST-1853: Add Product Review XAPI Middleware feat: Adds Product Review XAPI Middleware --- .../ModuleConstants.cs | 5 ++ .../Handlers/OrderChangedEventHandler.cs | 3 +- .../Repositories/CustomerReviewRepository.cs | 3 +- .../Services/RatingService.cs | 7 +- .../Extensions/ServiceCollectionExtensions.cs | 1 + .../Middleware/EvalProductRatingMiddleware.cs | 73 +++++++++++++++++++ .../EvalProductVendorRatingMiddleware.cs | 48 ++++++------ ...merce.CustomerReviews.ExperienceApi.csproj | 2 +- .../Api/CustomerReviewsModuleController.cs | 2 +- 9 files changed, 116 insertions(+), 28 deletions(-) create mode 100644 src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs diff --git a/src/VirtoCommerce.CustomerReviews.Core/ModuleConstants.cs b/src/VirtoCommerce.CustomerReviews.Core/ModuleConstants.cs index 71b446b..7e9494e 100644 --- a/src/VirtoCommerce.CustomerReviews.Core/ModuleConstants.cs +++ b/src/VirtoCommerce.CustomerReviews.Core/ModuleConstants.cs @@ -3,6 +3,11 @@ namespace VirtoCommerce.CustomerReviews.Core { + public static class ReviewEntityTypes + { + public const string Product = "Product"; + } + public static class ModuleConstants { public static class Security diff --git a/src/VirtoCommerce.CustomerReviews.Data/Handlers/OrderChangedEventHandler.cs b/src/VirtoCommerce.CustomerReviews.Data/Handlers/OrderChangedEventHandler.cs index b9fe1a7..48ea138 100644 --- a/src/VirtoCommerce.CustomerReviews.Data/Handlers/OrderChangedEventHandler.cs +++ b/src/VirtoCommerce.CustomerReviews.Data/Handlers/OrderChangedEventHandler.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Hangfire; +using VirtoCommerce.CustomerReviews.Core; using VirtoCommerce.CustomerReviews.Data.Models; using VirtoCommerce.CustomerReviews.Data.Repositories; using VirtoCommerce.OrdersModule.Core.Events; @@ -82,7 +83,7 @@ public virtual async Task TryToSendOrderNotificationsAsync(OrderRequestReviewJob CustomerOrderId = jobArgument.CustomerOrderId, ModifiedDate = DateTime.Now, EntityId = item.ProductId, - EntityType = "Product", + EntityType = ReviewEntityTypes.Product, ReviewsRequest = 0, StoreId = jobArgument.StoreId, UserId = jobArgument.CustomerId diff --git a/src/VirtoCommerce.CustomerReviews.Data/Repositories/CustomerReviewRepository.cs b/src/VirtoCommerce.CustomerReviews.Data/Repositories/CustomerReviewRepository.cs index a6784b2..b4a4cab 100644 --- a/src/VirtoCommerce.CustomerReviews.Data/Repositories/CustomerReviewRepository.cs +++ b/src/VirtoCommerce.CustomerReviews.Data/Repositories/CustomerReviewRepository.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using VirtoCommerce.CustomerReviews.Core; using VirtoCommerce.CustomerReviews.Data.Models; using VirtoCommerce.Platform.Data.Infrastructure; @@ -70,7 +71,7 @@ public async Task> GetReviewsWithEmptyAccessDate(Date !CustomerReviews.Any(cr => r.StoreId == cr.StoreId && r.EntityId == cr.EntityId && - r.EntityType == "Product" && + r.EntityType == ReviewEntityTypes.Product && cr.UserId == r.UserId)) .ToListAsync(); } diff --git a/src/VirtoCommerce.CustomerReviews.Data/Services/RatingService.cs b/src/VirtoCommerce.CustomerReviews.Data/Services/RatingService.cs index 79e8976..980a99c 100644 --- a/src/VirtoCommerce.CustomerReviews.Data/Services/RatingService.cs +++ b/src/VirtoCommerce.CustomerReviews.Data/Services/RatingService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using VirtoCommerce.CustomerReviews.Core; using VirtoCommerce.CustomerReviews.Core.Models; using VirtoCommerce.CustomerReviews.Core.Services; using VirtoCommerce.CustomerReviews.Data.Models; @@ -40,7 +41,7 @@ public RatingService( public Task CalculateAsync(string storeId) { - return Calculate(storeId, null, "Product"); + return Calculate(storeId, null, ReviewEntityTypes.Product); } public async Task CalculateAsync(ReviewStatusChangeData[] data) @@ -119,7 +120,7 @@ public async Task GetForStoreAsync(string storeId, string[] { using (var repository = _repositoryFactory()) { - var ratings = await repository.GetAsync(storeId, productIds, "Product"); + var ratings = await repository.GetAsync(storeId, productIds, ReviewEntityTypes.Product); return ratings.Select(x => new RatingProductDto { @@ -165,7 +166,7 @@ public async Task GetForCatalogAsync(string catalogId, string[ foreach (var store in stores) { - var ratings = await repository.GetAsync(store.Id, productIds, "Product"); + var ratings = await repository.GetAsync(store.Id, productIds, ReviewEntityTypes.Product); if (ratings.Any()) { result.AddRange(ratings.Select(x => new RatingStoreDto diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Extensions/ServiceCollectionExtensions.cs b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Extensions/ServiceCollectionExtensions.cs index e6074a7..d568468 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Extensions/ServiceCollectionExtensions.cs +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Extensions/ServiceCollectionExtensions.cs @@ -22,6 +22,7 @@ public static IServiceCollection AddExperienceApi(this IServiceCollection servic serviceCollection.AddSchemaBuilders(assemblyMarker); serviceCollection.AddPipeline(builder => { + builder.AddMiddleware(typeof(EvalProductRatingMiddleware)); builder.AddMiddleware(typeof(EvalProductVendorRatingMiddleware)); }); diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs new file mode 100644 index 0000000..590d182 --- /dev/null +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using PipelineNet.Middleware; +using VirtoCommerce.CustomerReviews.Core; +using VirtoCommerce.CustomerReviews.Core.Models; +using VirtoCommerce.CustomerReviews.Core.Services; +using VirtoCommerce.Platform.Core.Common; +using VirtoCommerce.Xapi.Core.Models; +using VirtoCommerce.XCatalog.Core.Models; +using VirtoCommerce.XDigitalCatalog.Queries; + +namespace VirtoCommerce.CustomerReviews.ExperienceApi.Middleware; + +public class EvalProductRatingMiddleware : IAsyncMiddleware +{ + private readonly IMapper _mapper; + private readonly IRatingService _ratingService; + + public EvalProductRatingMiddleware(IMapper mapper, IRatingService ratingService) + { + _mapper = mapper; + _ratingService = ratingService; + } + + public virtual async Task Run(SearchProductResponse parameter, Func next) + { + if (parameter == null) + { + throw new ArgumentNullException(nameof(parameter)); + } + + var query = parameter.Query; + if (query == null) + { + throw new OperationCanceledException("Query must be set"); + } + + var responseGroup = EnumUtility.SafeParse(query.GetResponseGroup(), ExpProductResponseGroup.None); + if (responseGroup.HasFlag(ExpProductResponseGroup.LoadRating) && parameter.Results.Any()) + { + await LoadProductRaiting(parameter, query); + } + + await next(parameter); + } + + protected virtual async Task LoadProductRaiting(SearchProductResponse parameter, SearchProductQuery query) + { + var productIds = parameter.Results.Select(product => product.Id).ToArray(); + var ratings = await _ratingService.GetForStoreAsync(query.StoreId, productIds, ReviewEntityTypes.Product); + + var ratingByIds = new Dictionary(); + foreach (var rating in ratings) + { + ratingByIds.Add(rating.EntityId, rating); + } + + if (ratingByIds.Count != 0) + { + parameter.Results + .Apply(product => + { + if (ratingByIds.TryGetValue(product.Id, out var rating)) + { + product.Rating = _mapper.Map(rating); + } + }); + } + } +} diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs index 26375ce..c64aee0 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs @@ -9,6 +9,7 @@ using VirtoCommerce.Platform.Core.Common; using VirtoCommerce.Xapi.Core.Models; using VirtoCommerce.XCatalog.Core.Models; +using VirtoCommerce.XDigitalCatalog.Queries; namespace VirtoCommerce.CustomerReviews.ExperienceApi.Middleware; @@ -39,34 +40,39 @@ public virtual async Task Run(SearchProductResponse parameter, Func product.Vendor != null).Select(product => product.Vendor).ToArray(); + await LoadVendorRaiting(parameter, query); + } - var ratingByIds = new Dictionary<(string, string), RatingEntityDto>(); + await next(parameter); + } - foreach (var vendorsByType in vendors.GroupBy(vendor => vendor.Type)) - { - var vendorType = vendorsByType.Key; - var vendorIds = vendorsByType.Select(vendor => vendor.Id).Distinct().ToArray(); - var ratings = await _ratingService.GetForStoreAsync(query.StoreId, vendorIds, vendorType); + protected virtual async Task LoadVendorRaiting(SearchProductResponse parameter, SearchProductQuery query) + { + var vendors = parameter.Results.Where(product => product.Vendor != null).Select(product => product.Vendor).ToArray(); - foreach (var rating in ratings) - { - ratingByIds.Add((rating.EntityId, rating.EntityType), rating); - } - } + var ratingByIds = new Dictionary<(string, string), RatingEntityDto>(); - if (ratingByIds.Any()) + foreach (var vendorsByType in vendors.GroupBy(vendor => vendor.Type)) + { + var vendorType = vendorsByType.Key; + var vendorIds = vendorsByType.Select(vendor => vendor.Id).Distinct().ToArray(); + var ratings = await _ratingService.GetForStoreAsync(query.StoreId, vendorIds, vendorType); + + foreach (var rating in ratings) { - parameter.Results - .Where(product => product.Vendor != null) - .Apply(product => - { - ratingByIds.TryGetValue((product.Vendor.Id, product.Vendor.Type), out var rating); - product.Vendor.Rating = _mapper.Map(rating); - }); + ratingByIds.Add((rating.EntityId, rating.EntityType), rating); } } - await next(parameter); + if (ratingByIds.Any()) + { + parameter.Results + .Where(product => product.Vendor != null) + .Apply(product => + { + ratingByIds.TryGetValue((product.Vendor.Id, product.Vendor.Type), out var rating); + product.Vendor.Rating = _mapper.Map(rating); + }); + } } } diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/VirtoCommerce.CustomerReviews.ExperienceApi.csproj b/src/VirtoCommerce.CustomerReviews.ExperienceApi/VirtoCommerce.CustomerReviews.ExperienceApi.csproj index 8e5c69e..7a8f9b2 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/VirtoCommerce.CustomerReviews.ExperienceApi.csproj +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/VirtoCommerce.CustomerReviews.ExperienceApi.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/VirtoCommerce.CustomerReviews.Web/Controllers/Api/CustomerReviewsModuleController.cs b/src/VirtoCommerce.CustomerReviews.Web/Controllers/Api/CustomerReviewsModuleController.cs index 55d8b24..07a1412 100644 --- a/src/VirtoCommerce.CustomerReviews.Web/Controllers/Api/CustomerReviewsModuleController.cs +++ b/src/VirtoCommerce.CustomerReviews.Web/Controllers/Api/CustomerReviewsModuleController.cs @@ -51,7 +51,7 @@ public async Task> GetCustomerR foreach (var review in reviews.Results) { var listItem = new CustomerReviewListItem(review); - if (review.EntityType == "Product") + if (review.EntityType == ReviewEntityTypes.Product) { listItem.StoreName = stores.FirstOrDefault(s => s.Id == review.StoreId)?.Name; } From 1aea391eec5520f1286802c084405325f86d83aa Mon Sep 17 00:00:00 2001 From: Oleg Zhuk Date: Tue, 24 Sep 2024 08:56:30 +0200 Subject: [PATCH 2/4] fix code smell --- .../Middleware/EvalProductVendorRatingMiddleware.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs index c64aee0..7bd7d05 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs @@ -70,8 +70,10 @@ protected virtual async Task LoadVendorRaiting(SearchProductResponse parameter, .Where(product => product.Vendor != null) .Apply(product => { - ratingByIds.TryGetValue((product.Vendor.Id, product.Vendor.Type), out var rating); - product.Vendor.Rating = _mapper.Map(rating); + if (ratingByIds.TryGetValue((product.Vendor.Id, product.Vendor.Type), out var rating)) + { + product.Vendor.Rating = _mapper.Map(rating); + } }); } } From 3ebc83f915107da43cd1111b1198108c7cceb1a6 Mon Sep 17 00:00:00 2001 From: Oleg Zhuk Date: Tue, 24 Sep 2024 09:14:21 +0200 Subject: [PATCH 3/4] improve code. bump VirtoCommerce.XCatalog.Core to 3.810.0 --- .../Middleware/EvalProductRatingMiddleware.cs | 5 +---- .../Middleware/EvalProductVendorRatingMiddleware.cs | 5 +---- .../Middleware/EvalVendorRatingMiddleware.cs | 7 ++----- .../VirtoCommerce.CustomerReviews.ExperienceApi.csproj | 2 +- src/VirtoCommerce.CustomerReviews.Web/module.manifest | 2 +- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs index 590d182..15a2d7b 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs @@ -27,10 +27,7 @@ public EvalProductRatingMiddleware(IMapper mapper, IRatingService ratingService) public virtual async Task Run(SearchProductResponse parameter, Func next) { - if (parameter == null) - { - throw new ArgumentNullException(nameof(parameter)); - } + ArgumentNullException.ThrowIfNull(parameter); var query = parameter.Query; if (query == null) diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs index 7bd7d05..efaf6cb 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs @@ -26,10 +26,7 @@ public EvalProductVendorRatingMiddleware(IMapper mapper, IRatingService ratingSe public virtual async Task Run(SearchProductResponse parameter, Func next) { - if (parameter == null) - { - throw new ArgumentNullException(nameof(parameter)); - } + ArgumentNullException.ThrowIfNull(parameter); var query = parameter.Query; if (query == null) diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalVendorRatingMiddleware.cs b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalVendorRatingMiddleware.cs index 016dff9..a25c6ad 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalVendorRatingMiddleware.cs +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalVendorRatingMiddleware.cs @@ -22,12 +22,9 @@ public EvalVendorRatingMiddleware(IMapper mapper, IRatingService ratingService) public virtual async Task Run(VendorAggregate parameter, Func next) { - if (parameter == null) - { - throw new ArgumentNullException(nameof(parameter)); - } + ArgumentNullException.ThrowIfNull(parameter); - var ratings = await _ratingService.GetRatingsAsync(new [] { parameter.Member.Id }, parameter.Member.MemberType); + var ratings = await _ratingService.GetRatingsAsync(new[] { parameter.Member.Id }, parameter.Member.MemberType); parameter.Ratings = ratings.Select(rating => _mapper.Map(rating)).ToArray(); await next(parameter); diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/VirtoCommerce.CustomerReviews.ExperienceApi.csproj b/src/VirtoCommerce.CustomerReviews.ExperienceApi/VirtoCommerce.CustomerReviews.ExperienceApi.csproj index 7a8f9b2..b73debd 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/VirtoCommerce.CustomerReviews.ExperienceApi.csproj +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/VirtoCommerce.CustomerReviews.ExperienceApi.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/VirtoCommerce.CustomerReviews.Web/module.manifest b/src/VirtoCommerce.CustomerReviews.Web/module.manifest index db7f730..c130a64 100644 --- a/src/VirtoCommerce.CustomerReviews.Web/module.manifest +++ b/src/VirtoCommerce.CustomerReviews.Web/module.manifest @@ -15,7 +15,7 @@ Rating and Reviews - Rating and Reviews + Enables customers to share their feedback on products and vendors after making a purchase, helping others make informed decisions. Egidijus Mazeika Andrew Nikutin From fc68ed03dba8bfd591a63c5001faab056364ccfa Mon Sep 17 00:00:00 2001 From: Oleg Zhuk Date: Tue, 24 Sep 2024 09:20:32 +0200 Subject: [PATCH 4/4] Code refactoring --- .../Middleware/EvalProductRatingMiddleware.cs | 15 +++++++-------- .../EvalProductVendorRatingMiddleware.cs | 7 ++++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs index 15a2d7b..39c248f 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductRatingMiddleware.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AutoMapper; using PipelineNet.Middleware; using VirtoCommerce.CustomerReviews.Core; -using VirtoCommerce.CustomerReviews.Core.Models; using VirtoCommerce.CustomerReviews.Core.Services; using VirtoCommerce.Platform.Core.Common; using VirtoCommerce.Xapi.Core.Models; @@ -47,16 +45,17 @@ public virtual async Task Run(SearchProductResponse parameter, Func product.Id).ToArray(); - var ratings = await _ratingService.GetForStoreAsync(query.StoreId, productIds, ReviewEntityTypes.Product); - - var ratingByIds = new Dictionary(); - foreach (var rating in ratings) + if (productIds.Length == 0) { - ratingByIds.Add(rating.EntityId, rating); + return; } - if (ratingByIds.Count != 0) + var ratings = await _ratingService.GetForStoreAsync(query.StoreId, productIds, ReviewEntityTypes.Product); + + if (ratings.Length != 0) { + var ratingByIds = ratings.ToDictionary(x => x.EntityId); + parameter.Results .Apply(product => { diff --git a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs index efaf6cb..fc57c72 100644 --- a/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs +++ b/src/VirtoCommerce.CustomerReviews.ExperienceApi/Middleware/EvalProductVendorRatingMiddleware.cs @@ -47,6 +47,11 @@ protected virtual async Task LoadVendorRaiting(SearchProductResponse parameter, { var vendors = parameter.Results.Where(product => product.Vendor != null).Select(product => product.Vendor).ToArray(); + if (vendors.Length == 0) + { + return; + } + var ratingByIds = new Dictionary<(string, string), RatingEntityDto>(); foreach (var vendorsByType in vendors.GroupBy(vendor => vendor.Type)) @@ -61,7 +66,7 @@ protected virtual async Task LoadVendorRaiting(SearchProductResponse parameter, } } - if (ratingByIds.Any()) + if (ratingByIds.Count != 0) { parameter.Results .Where(product => product.Vendor != null)