workarea-reviews 3.0.10 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.eslintrc.json +35 -0
- data/.github/workflows/ci.yml +60 -0
- data/.gitignore +1 -2
- data/.rubocop.yml +3 -0
- data/.stylelintrc.json +8 -0
- data/CHANGELOG.md +5 -23
- data/Gemfile +4 -2
- data/Rakefile +4 -5
- data/app/controllers/workarea/admin/reports_controller.decorator +10 -0
- data/app/helpers/workarea/admin/reviews_helper.rb +6 -5
- data/app/helpers/workarea/storefront/reviews_helper.rb +1 -3
- data/app/helpers/workarea/storefront/reviews_schema_org_helper.rb +34 -0
- data/app/mailers/workarea/admin/status_report_mailer.decorator +2 -2
- data/app/models/workarea/insights/most_active_reviewers.rb +34 -0
- data/app/models/workarea/insights/most_reviewed_products.rb +33 -0
- data/app/models/workarea/insights/top_rated_products.rb +33 -0
- data/app/models/workarea/review.rb +5 -16
- data/app/queries/workarea/reports/reviews_by_product.rb +86 -0
- data/app/queries/workarea/reports/reviews_by_user.rb +71 -0
- data/app/seeds/{review_seeds.rb → workarea/review_seeds.rb} +0 -0
- data/app/view_models/workarea/admin/dashboards/reports_view_model.decorator +11 -0
- data/app/view_models/workarea/admin/reports/reviews_by_product_view_model.rb +27 -0
- data/app/views/workarea/admin/dashboards/_reviews_by_product_card.html.haml +17 -0
- data/app/views/workarea/admin/insights/_most_active_reviewers.html.haml +22 -0
- data/app/views/workarea/admin/insights/_most_reviewed_products.html.haml +22 -0
- data/app/views/workarea/admin/insights/_top_rated_products.html.haml +22 -0
- data/app/views/workarea/admin/reports/reviews_by_product.html.haml +44 -0
- data/app/views/workarea/storefront/products/_rating.html.haml +1 -2
- data/app/views/workarea/storefront/products/_reviews.html.haml +5 -5
- data/app/views/workarea/storefront/products/_reviews_aggregate.html.haml +2 -2
- data/app/views/workarea/storefront/review_mailer/review_request.html.haml +2 -0
- data/app/views/workarea/storefront/review_requests/show.html.haml +2 -1
- data/config/initializers/append_points.rb +5 -0
- data/config/initializers/configuration.rb +3 -1
- data/config/locales/en.yml +46 -0
- data/config/routes.rb +4 -0
- data/lib/workarea/reviews/engine.rb +4 -0
- data/lib/workarea/reviews/version.rb +1 -1
- data/package.json +9 -0
- data/test/dummy/config/initializers/session_store.rb +3 -1
- data/test/helpers/workarea/storefront/reviews_helper_test.rb +1 -0
- data/test/integration/workarea/admin/reviews_integration_test.rb +5 -1
- data/test/models/workarea/insights/most_active_reviewers_test.rb +32 -0
- data/test/models/workarea/insights/most_reviewed_products_test.rb +32 -0
- data/test/models/workarea/insights/top_rated_products_test.rb +49 -0
- data/test/models/workarea/review_test.rb +29 -18
- data/test/queries/workarea/reports/reviews_by_product_test.rb +104 -0
- data/test/queries/workarea/reports/reviews_by_user_test.rb +102 -0
- data/test/system/workarea/admin/reviews_by_product_system_test.rb +60 -0
- data/workarea-reviews.gemspec +1 -1
- data/yarn.lock +3265 -0
- metadata +31 -7
- data/app/controllers/workarea/admin/import_reviews_controller.rb +0 -35
- data/test/helpers/workarea/admin/reviews_helper_test.rb +0 -17
@@ -0,0 +1,71 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Reports
|
3
|
+
class ReviewsByUser
|
4
|
+
include Report
|
5
|
+
|
6
|
+
self.reporting_class = Review
|
7
|
+
self.sort_fields = %w(reviews verified average_rating activity_score)
|
8
|
+
|
9
|
+
def aggregation
|
10
|
+
[filter_results, project_used_fields, group_by_email, project_fields]
|
11
|
+
end
|
12
|
+
|
13
|
+
def filter_results
|
14
|
+
{
|
15
|
+
'$match' => {
|
16
|
+
'created_at' => { '$gte' => starts_at, '$lte' => ends_at },
|
17
|
+
**approval_query
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def project_used_fields
|
23
|
+
{
|
24
|
+
'$project' => {
|
25
|
+
'email' => 1,
|
26
|
+
'user_id' => 1,
|
27
|
+
'rating' => 1,
|
28
|
+
'verified' => 1
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def group_by_email
|
34
|
+
{
|
35
|
+
'$group' => {
|
36
|
+
'_id' => '$email',
|
37
|
+
'user_id' => { '$max' => '$user_id' },
|
38
|
+
'reviews' => { '$sum' => 1 },
|
39
|
+
'verified' => { '$sum' => { '$cond' => { 'if' => '$verified', 'then' => 1, 'else' => 0 } } },
|
40
|
+
'rating_tally' => { '$sum' => '$rating' }
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def project_fields
|
46
|
+
{
|
47
|
+
'$project' => {
|
48
|
+
'email' => 1,
|
49
|
+
'user_id' => 1,
|
50
|
+
'reviews' => 1,
|
51
|
+
'verified' => 1,
|
52
|
+
'average_rating' => { '$divide' => ['$rating_tally', '$reviews'] },
|
53
|
+
'activity_score' => { '$sum' => ['$reviews', { '$multiply' => ['$verified', 0.75] }] }
|
54
|
+
}
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def approval_query
|
61
|
+
if params[:results_filter] == 'approved'
|
62
|
+
{ approved: true }
|
63
|
+
elsif params[:results_filter] == 'unapproved'
|
64
|
+
{ approved: false }
|
65
|
+
else
|
66
|
+
{}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
File without changes
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate Admin::Dashboards::ReportsViewModel, with: :reviews do
|
3
|
+
def reviews_by_product
|
4
|
+
@reviews_by_product ||=
|
5
|
+
Admin::Reports::ReviewsByProductViewModel.wrap(
|
6
|
+
Workarea::Reports::ReviewsByProduct.new(options),
|
7
|
+
options
|
8
|
+
)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Admin
|
3
|
+
module Reports
|
4
|
+
class ReviewsByProductViewModel < ApplicationViewModel
|
5
|
+
def results
|
6
|
+
@results ||= model.results.map do |result|
|
7
|
+
product = products.detect { |p| p.id == result['_id'] }
|
8
|
+
OpenStruct.new(
|
9
|
+
{ product: product }
|
10
|
+
.merge(result)
|
11
|
+
.merge(
|
12
|
+
average_rating: result['average_rating'].round(2),
|
13
|
+
weighted_average_rating: result['weighted_average_rating'].round(2)
|
14
|
+
)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def products
|
20
|
+
@products ||= Catalog::Product.any_in(
|
21
|
+
id: model.results.map { |r| r['_id'] }
|
22
|
+
).to_a
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
.grid__cell
|
2
|
+
.card{ class: card_classes(:reviews_by_product_report, local_assigns[:active]) }
|
3
|
+
= link_to reviews_by_product_report_path, class: 'card__header' do
|
4
|
+
%span.card__header-text= t('workarea.admin.reports.reviews_by_product.title')
|
5
|
+
= inline_svg 'workarea/admin/icons/insights.svg', class: 'card__icon'
|
6
|
+
|
7
|
+
.card__body
|
8
|
+
.card__centered-content
|
9
|
+
%table
|
10
|
+
%tbody
|
11
|
+
- @dashboard.reviews_by_product.results.take(4).each do |result|
|
12
|
+
%tr
|
13
|
+
%td= result.product.present? ? result.product.name : result._id
|
14
|
+
%td.align-right= number_with_delimiter result.reviews
|
15
|
+
|
16
|
+
= link_to reviews_by_product_report_path, class: 'card__button' do
|
17
|
+
%span.button.button--small= t('workarea.admin.dashboards.reports.view_full_report')
|
@@ -0,0 +1,22 @@
|
|
1
|
+
.insight
|
2
|
+
.insight__date
|
3
|
+
%span.insight__period= insight.reporting_on.strftime('%B %Y')
|
4
|
+
.insight__heading= t('workarea.admin.insights.most_active_reviewers.title')
|
5
|
+
.insight__body
|
6
|
+
%p.insight__note= t('workarea.admin.insights.most_active_reviewers.info')
|
7
|
+
%table
|
8
|
+
%thead
|
9
|
+
%tr
|
10
|
+
%th.align-center= t('workarea.admin.insights.most_active_reviewers.user')
|
11
|
+
%th.align-center= t('workarea.admin.insights.most_active_reviewers.reviews')
|
12
|
+
%th.align-center= t('workarea.admin.insights.most_active_reviewers.verified')
|
13
|
+
%tbody
|
14
|
+
- insight.results.each do |result|
|
15
|
+
%tr
|
16
|
+
%td.align-center
|
17
|
+
- if result.user.present?
|
18
|
+
= link_to result.user.name, user_path(result.user)
|
19
|
+
- else
|
20
|
+
= result.email
|
21
|
+
%td.align-center= number_with_delimiter result.reviews
|
22
|
+
%td.align-center= number_with_delimiter result.verified
|
@@ -0,0 +1,22 @@
|
|
1
|
+
.insight
|
2
|
+
.insight__date
|
3
|
+
%span.insight__period= insight.reporting_on.strftime('%B %Y')
|
4
|
+
.insight__heading= t('workarea.admin.insights.most_reviewed_products.title')
|
5
|
+
.insight__body
|
6
|
+
%p.insight__note= t('workarea.admin.insights.most_reviewed_products.info')
|
7
|
+
.grid.grid--large.grid--center
|
8
|
+
- insight.results.each do |result|
|
9
|
+
.grid__cell.grid__cell--50.grid__cell--25-at-wide.align-center
|
10
|
+
.insight__product
|
11
|
+
- if result.product.blank?
|
12
|
+
= image_tag(product_image_url(Workarea::Catalog::ProductPlaceholderImage.cached, :small), alt: result.product_id, class: 'insight__product-image')
|
13
|
+
- else
|
14
|
+
= link_to catalog_product_path(result.product) do
|
15
|
+
= image_tag(product_image_url(result.product.primary_image, :small), alt: result.product.name, class: 'insight__product-image')
|
16
|
+
.insight__product-name
|
17
|
+
- if result.product.blank?
|
18
|
+
= result.product_id
|
19
|
+
- else
|
20
|
+
= link_to result.product.name, catalog_product_path(result.product)
|
21
|
+
.insight__product-info
|
22
|
+
%strong= t('workarea.admin.insights.most_reviewed_products.reviews', count: result.reviews)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
.insight
|
2
|
+
.insight__date
|
3
|
+
%span.insight__period= insight.reporting_on.strftime('%B %Y')
|
4
|
+
.insight__heading= t('workarea.admin.insights.top_rated_products.title')
|
5
|
+
.insight__body
|
6
|
+
%p.insight__note= t('workarea.admin.insights.top_rated_products.info')
|
7
|
+
.grid.grid--large.grid--center
|
8
|
+
- insight.results.each do |result|
|
9
|
+
.grid__cell.grid__cell--50.grid__cell--25-at-wide.align-center
|
10
|
+
.insight__product
|
11
|
+
- if result.product.blank?
|
12
|
+
= image_tag(product_image_url(Workarea::Catalog::ProductPlaceholderImage.cached, :small), alt: result.product_id, class: 'insight__product-image')
|
13
|
+
- else
|
14
|
+
= link_to catalog_product_path(result.product) do
|
15
|
+
= image_tag(product_image_url(result.product.primary_image, :small), alt: result.product.name, class: 'insight__product-image')
|
16
|
+
.insight__product-name
|
17
|
+
- if result.product.blank?
|
18
|
+
= result.product_id
|
19
|
+
- else
|
20
|
+
= link_to result.product.name, catalog_product_path(result.product)
|
21
|
+
.insight__product-info
|
22
|
+
%strong= t('workarea.admin.insights.top_rated_products.rating', rating: number_with_delimiter(result.weighted_average_rating.round(2)))
|
@@ -0,0 +1,44 @@
|
|
1
|
+
- @page_title = t('workarea.admin.reports.reviews_by_product.title')
|
2
|
+
|
3
|
+
.view
|
4
|
+
.view__header
|
5
|
+
.view__heading
|
6
|
+
= link_to "↑ #{t('workarea.admin.reports.all_reports')}", reports_dashboards_path
|
7
|
+
%h1.heading.heading--no-margin= t('workarea.admin.reports.reviews_by_product.title')
|
8
|
+
%p= t('workarea.admin.reports.reference_link_html', path: reference_report_path)
|
9
|
+
|
10
|
+
.view__container.view__container--narrow
|
11
|
+
.browsing-controls.browsing-controls--with-divider.browsing-controls--center.browsing-controls--filters-displayed
|
12
|
+
= form_tag reviews_by_product_report_path, method: 'get', class: 'browsing-controls__form' do
|
13
|
+
= render 'workarea/admin/shared/date_selector', starts_at: @report.starts_at, ends_at: @report.ends_at
|
14
|
+
|
15
|
+
.browsing-controls__filter
|
16
|
+
.property.property--inline
|
17
|
+
= label_tag 'results_filter', t('workarea.admin.reports.reviews_by_product.results_filter'), class: 'property__name'
|
18
|
+
= select_tag 'results_filter', options_for_select(reviews_report_filter_options, params[:results_filter]), data: { form_submitting_control: '' }
|
19
|
+
|
20
|
+
|
21
|
+
.browsing-controls__count
|
22
|
+
= render_reports_results_message(@report)
|
23
|
+
= render 'workarea/admin/reports/export', report: @report
|
24
|
+
|
25
|
+
%table
|
26
|
+
%thead
|
27
|
+
%tr
|
28
|
+
%th= t('workarea.admin.fields.product')
|
29
|
+
%th.align-center= link_to_reports_sorting t('workarea.admin.fields.reviews'), report: @report, sort_by: 'reviews'
|
30
|
+
%th.align-center= link_to_reports_sorting t('workarea.admin.fields.verified_reviews'), report: @report, sort_by: 'verified'
|
31
|
+
%th.align-center= link_to_reports_sorting t('workarea.admin.fields.average_rating'), report: @report, sort_by: 'average_rating'
|
32
|
+
%th.align-center= link_to_reports_sorting t('workarea.admin.fields.weighted_average_rating'), report: @report, sort_by: 'weighted_average_rating'
|
33
|
+
%tbody
|
34
|
+
- @report.results.each do |result|
|
35
|
+
%tr
|
36
|
+
%td
|
37
|
+
- if result.product.present?
|
38
|
+
= link_to result.product.name, catalog_product_path(result.product)
|
39
|
+
- else
|
40
|
+
= result._id
|
41
|
+
%td.align-center= number_with_delimiter result.reviews
|
42
|
+
%td.align-center= number_with_delimiter result.verified
|
43
|
+
%td.align-center= number_with_delimiter result.average_rating
|
44
|
+
%td.align-center= number_with_delimiter result.weighted_average_rating
|
@@ -1,4 +1,4 @@
|
|
1
|
-
%p.rating{ title: "#{rating.round(2)} #{t('workarea.storefront.reviews.out_of')} #{t('workarea.storefront.reviews.stars', count: 5)}"
|
1
|
+
%p.rating{ title: "#{rating.round(2)} #{t('workarea.storefront.reviews.out_of')} #{t('workarea.storefront.reviews.stars', count: 5)}" }
|
2
2
|
- full_star_count.times do
|
3
3
|
= inline_svg 'workarea/storefront/icons/star.svg', class: 'rating__star'
|
4
4
|
- if half_star_size > 0
|
@@ -6,5 +6,4 @@
|
|
6
6
|
- empty_star_count.times do
|
7
7
|
= inline_svg 'workarea/storefront/icons/empty_star.svg', class: 'rating__star'
|
8
8
|
%span.rating__text.visually-hidden
|
9
|
-
%meta{ itemprop: 'ratingValue', content: rating.round(2) }
|
10
9
|
#{rating.round(2)} #{t('workarea.storefront.reviews.out_of')} #{t('workarea.storefront.reviews.stars', count: 5)}
|
@@ -10,14 +10,14 @@
|
|
10
10
|
|
11
11
|
%ol.reviews__review-group
|
12
12
|
- product.reviews.each do |review|
|
13
|
-
%li.reviews__review{
|
13
|
+
%li.reviews__review{ data: { product_review_section_entry: { rating: review.rating.to_f, createdAt: review.created_at.to_i }.to_json } }
|
14
14
|
= rating_stars(review.rating)
|
15
15
|
|
16
|
-
%h3.reviews__review-title
|
17
|
-
%p.reviews__review-body
|
16
|
+
%h3.reviews__review-title= review.title
|
17
|
+
%p.reviews__review-body= review.body
|
18
18
|
.reviews__review-meta
|
19
|
-
%p.reviews__review-author
|
20
|
-
%span
|
19
|
+
%p.reviews__review-author
|
20
|
+
%span= review.user_info
|
21
21
|
- if review.verified?
|
22
22
|
%p.reviews__review-verified= t('workarea.storefront.reviews.verified_purchaser')
|
23
23
|
%p.reviews__review-date= local_time(review.created_at, format: :long, itemprop: 'datePublished')
|
@@ -1,5 +1,5 @@
|
|
1
1
|
- if product.has_reviews?
|
2
|
-
.reviews-aggregate
|
2
|
+
.reviews-aggregate
|
3
3
|
= link_to "#{product_path(product, product.browse_link_options)}#reviews", data: { scroll_to_button: '' }, class: 'reviews-aggregate__rating-link' do
|
4
4
|
= rating_stars(product.average_rating, aggregate: true)
|
5
5
|
|
@@ -7,7 +7,7 @@
|
|
7
7
|
%span.reviews-aggregate__count
|
8
8
|
%span.reviews-aggregate__read= t('workarea.storefront.reviews.read_reviews')
|
9
9
|
= surround '(', ')' do
|
10
|
-
%span
|
10
|
+
%span= product.total_reviews
|
11
11
|
|
12
12
|
= link_to t('workarea.storefront.reviews.write_a_review'), new_product_review_path(product), class: 'reviews-aggregate__write-action', data: { dialog_button: '' }
|
13
13
|
- if display_purchase_requirement_message
|
@@ -5,7 +5,8 @@
|
|
5
5
|
|
6
6
|
.grid
|
7
7
|
.grid__cell.grid__cell--25
|
8
|
-
.product-summary
|
8
|
+
.product-summary
|
9
|
+
= render_schema_org(product_schema(@product))
|
9
10
|
= render 'workarea/storefront/products/summary', product: @product
|
10
11
|
|
11
12
|
.grid__cell.grid__cell--75
|
@@ -1,5 +1,5 @@
|
|
1
1
|
Workarea.configure do |config|
|
2
|
-
config.seeds
|
2
|
+
config.seeds.insert('Workarea::InsightsSeeds', 'Workarea::ReviewSeeds')
|
3
3
|
|
4
4
|
config.jump_to_navigation.merge!('Product Reviews' => :reviews_path)
|
5
5
|
|
@@ -11,6 +11,8 @@ Workarea.configure do |config|
|
|
11
11
|
|
12
12
|
config.data_file_ignored_fields << %w(total_reviews average_rating)
|
13
13
|
|
14
|
+
config.insights_model_classes << 'Workarea::User'
|
15
|
+
|
14
16
|
# The amount of time before a Review::Request will auto expire
|
15
17
|
# and be removed from the collection
|
16
18
|
config.review_request_ttl = 6.months
|
data/config/locales/en.yml
CHANGED
@@ -15,12 +15,55 @@ en:
|
|
15
15
|
marketing:
|
16
16
|
reviews: Product reviews
|
17
17
|
fields:
|
18
|
+
average_rating: Average Rating
|
18
19
|
body: Body
|
19
20
|
title: Title
|
20
21
|
rating: Rating
|
22
|
+
reviews: Reviews
|
21
23
|
approved: Approved
|
22
24
|
auto_approve: Approve All Reviews
|
23
25
|
verified: Verified Purchaser
|
26
|
+
verified_reviews: Verified
|
27
|
+
weighted_average_rating: Weighted Average Rating
|
28
|
+
insights:
|
29
|
+
most_active_reviewers:
|
30
|
+
info: Your customers that are submitting the most reviews.
|
31
|
+
reviews: Reviews
|
32
|
+
title: Most Active Reviewers
|
33
|
+
user: User
|
34
|
+
verified: Verified Reviews
|
35
|
+
most_reviewed_products:
|
36
|
+
title: Most Reviewed Products
|
37
|
+
info: Your products that have received the most reviews.
|
38
|
+
reviews:
|
39
|
+
one: '%{count} Review'
|
40
|
+
other: '%{count} Reviews'
|
41
|
+
top_rated_products:
|
42
|
+
title: Top Rated Products
|
43
|
+
info: Your products with the highest weighted average rating.
|
44
|
+
rating: '%{rating} Rating'
|
45
|
+
reports:
|
46
|
+
reference:
|
47
|
+
terms:
|
48
|
+
average_rating:
|
49
|
+
name: Average Rating
|
50
|
+
description: The sum of all review ratings divided by the total number of reviews.
|
51
|
+
reviews:
|
52
|
+
name: Reviews
|
53
|
+
description: The total number of reviews.
|
54
|
+
verified:
|
55
|
+
name: Verified
|
56
|
+
description: The total number of reviews made by customers who have purchased the product.
|
57
|
+
weighted_average_rating:
|
58
|
+
name: Weighted Average Rating
|
59
|
+
description: A base rating of 3.0, adjusted up or down by each review rating.
|
60
|
+
reviews_by_product:
|
61
|
+
filters:
|
62
|
+
all: All Reviews
|
63
|
+
approved: Approved Reviews
|
64
|
+
unapproved: Unapproved Reviews
|
65
|
+
results_filter: Results Filter
|
66
|
+
title: Reviews By Product
|
24
67
|
reviews:
|
25
68
|
approved: Approved
|
26
69
|
catalog_products_cards:
|
@@ -66,6 +109,9 @@ en:
|
|
66
109
|
review_request:
|
67
110
|
subject: Review your recent purchase of %{product}
|
68
111
|
link: Write a review
|
112
|
+
email_action:
|
113
|
+
name: Write Review
|
114
|
+
description: Please review your recent purchase
|
69
115
|
fields:
|
70
116
|
username: Username
|
71
117
|
review_requests:
|
data/config/routes.rb
CHANGED
@@ -5,7 +5,11 @@ module Workarea
|
|
5
5
|
isolate_namespace Workarea::Reviews
|
6
6
|
|
7
7
|
config.to_prepare do
|
8
|
+
Admin::ApplicationController.helper(Admin::ReviewsHelper)
|
8
9
|
Storefront::ApplicationController.helper(Storefront::ReviewsHelper)
|
10
|
+
Workarea::Storefront::ApplicationController.helper(
|
11
|
+
Workarea::Storefront::ReviewsSchemaOrgHelper
|
12
|
+
)
|
9
13
|
end
|
10
14
|
end
|
11
15
|
end
|