workarea-reviews 3.0.8 → 3.1.2

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.json +35 -0
  3. data/.github/workflows/ci.yml +60 -0
  4. data/.gitignore +1 -2
  5. data/.rubocop.yml +3 -0
  6. data/.stylelintrc.json +8 -0
  7. data/CHANGELOG.md +24 -12
  8. data/Gemfile +4 -2
  9. data/Rakefile +4 -5
  10. data/app/controllers/workarea/admin/reports_controller.decorator +10 -0
  11. data/app/helpers/workarea/admin/reviews_helper.rb +6 -5
  12. data/app/helpers/workarea/storefront/reviews_helper.rb +1 -3
  13. data/app/helpers/workarea/storefront/reviews_schema_org_helper.rb +34 -0
  14. data/app/mailers/workarea/admin/status_report_mailer.decorator +2 -2
  15. data/app/models/workarea/insights/most_active_reviewers.rb +34 -0
  16. data/app/models/workarea/insights/most_reviewed_products.rb +33 -0
  17. data/app/models/workarea/insights/top_rated_products.rb +33 -0
  18. data/app/models/workarea/review.rb +5 -16
  19. data/app/queries/workarea/reports/reviews_by_product.rb +86 -0
  20. data/app/queries/workarea/reports/reviews_by_user.rb +71 -0
  21. data/app/seeds/{reviews.rb → workarea/review_seeds.rb} +17 -6
  22. data/app/view_models/workarea/admin/dashboards/reports_view_model.decorator +11 -0
  23. data/app/view_models/workarea/admin/reports/reviews_by_product_view_model.rb +27 -0
  24. data/app/view_models/workarea/storefront/review_view_model.rb +9 -3
  25. data/app/views/workarea/admin/dashboards/_reviews_by_product_card.html.haml +17 -0
  26. data/app/views/workarea/admin/insights/_most_active_reviewers.html.haml +22 -0
  27. data/app/views/workarea/admin/insights/_most_reviewed_products.html.haml +22 -0
  28. data/app/views/workarea/admin/insights/_top_rated_products.html.haml +22 -0
  29. data/app/views/workarea/admin/reports/reviews_by_product.html.haml +44 -0
  30. data/app/views/workarea/storefront/products/_rating.html.haml +1 -2
  31. data/app/views/workarea/storefront/products/_reviews.html.haml +5 -5
  32. data/app/views/workarea/storefront/products/_reviews_aggregate.html.haml +2 -2
  33. data/app/views/workarea/storefront/review_mailer/review_request.html.haml +2 -0
  34. data/app/views/workarea/storefront/review_requests/show.html.haml +2 -1
  35. data/config/initializers/append_points.rb +5 -0
  36. data/config/initializers/configuration.rb +3 -1
  37. data/config/initializers/scheduled_jobs.rb +8 -6
  38. data/config/locales/en.yml +46 -0
  39. data/config/routes.rb +4 -0
  40. data/lib/workarea/reviews/engine.rb +4 -0
  41. data/lib/workarea/reviews/version.rb +1 -1
  42. data/package.json +9 -0
  43. data/test/dummy/config/initializers/session_store.rb +3 -1
  44. data/test/helpers/workarea/storefront/reviews_helper_test.rb +1 -1
  45. data/test/integration/workarea/admin/reviews_integration_test.rb +5 -1
  46. data/test/models/workarea/insights/most_active_reviewers_test.rb +32 -0
  47. data/test/models/workarea/insights/most_reviewed_products_test.rb +32 -0
  48. data/test/models/workarea/insights/top_rated_products_test.rb +49 -0
  49. data/test/models/workarea/review_test.rb +29 -18
  50. data/test/queries/workarea/reports/reviews_by_product_test.rb +104 -0
  51. data/test/queries/workarea/reports/reviews_by_user_test.rb +102 -0
  52. data/test/system/workarea/admin/reviews_by_product_system_test.rb +60 -0
  53. data/test/view_models/workarea/storefront/review_view_model_test.rb +46 -0
  54. data/workarea-reviews.gemspec +1 -1
  55. data/yarn.lock +3265 -0
  56. metadata +33 -8
  57. data/app/controllers/workarea/admin/import_reviews_controller.rb +0 -35
  58. 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
@@ -3,17 +3,26 @@ module Workarea
3
3
  def perform
4
4
  puts 'Adding reviews...'
5
5
 
6
- Workarea::Catalog::Product.all.each_by(100) do |product|
7
- rand(10).times { create_review(product) }
6
+ Sidekiq::Callbacks.disable do
7
+ create_reviews_for_catalog
8
+ create_review_request_email_content
8
9
  end
9
-
10
- create_review_request_email_content
11
10
  end
12
11
 
13
12
  private
14
13
 
14
+ def create_reviews_for_catalog
15
+ Workarea::Catalog::Product.all.each_by(100) do |product|
16
+ reviews = Array.new(rand(10)) { create_review(product) }
17
+ next unless reviews.size > 0
18
+
19
+ Workarea::Review.collection.insert_many(reviews.map(&:as_document))
20
+ UpdateProductReviewData.perform_async(product.id)
21
+ end
22
+ end
23
+
15
24
  def create_review(product)
16
- Workarea::Review.create!(
25
+ Workarea::Review.new(
17
26
  product_id: product.id,
18
27
  user_id: BSON::ObjectId.new,
19
28
  rating: rand(5) + 1,
@@ -21,7 +30,9 @@ module Workarea
21
30
  body: Faker::Lorem.paragraph,
22
31
  approved: [true, false].sample,
23
32
  user_info: Faker::Internet.user_name,
24
- verified: [true, false].sample
33
+ verified: [true, false].sample,
34
+ created_at: rand(45).days.ago,
35
+ updated_at: Time.current
25
36
  )
26
37
  end
27
38
 
@@ -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
@@ -2,12 +2,18 @@ module Workarea
2
2
  module Storefront
3
3
  class ReviewViewModel < ApplicationViewModel
4
4
  def requires_public_info?
5
- anonymous? || user.public_info.blank?
5
+ anonymous? || user&.public_info.blank?
6
+ end
7
+
8
+ def anonymous?
9
+ model.anonymous? || user.nil?
6
10
  end
7
11
 
8
12
  def user
9
- return if anonymous?
10
- @user ||= User.find(user_id)
13
+ return if model.anonymous?
14
+ return @user if defined?(@user)
15
+
16
+ @user = User.find(user_id) rescue nil
11
17
  end
12
18
  end
13
19
  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)}", itemprop: itemprop, itemscope: true, itemtype: itemtype }
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{ itemprop: 'review', itemscope: true, itemtype: 'http://schema.org/Review', data: { product_review_section_entry: { rating: review.rating.to_f, createdAt: review.created_at.to_i }.to_json } }
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{ itemprop: 'name' }= review.title
17
- %p.reviews__review-body{ itemprop: 'reviewBody' }= 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{ itemprop: 'author', itemscope: true, itemtype: 'http://schema.org/Person' }
20
- %span{ itemprop: 'name' }= review.user_info
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{ itemprop: 'aggregateRating', itemscope: true, itemtype: 'http://schema.org/AggregateRating' }
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{ itemprop: 'reviewCount' }= product.total_reviews
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
@@ -1,3 +1,5 @@
1
+ = render_schema_org(email_action_schema(review_request_url(@request.token), t('workarea.storefront.email.review_request.email_action.name'), t('workarea.storefront.email.review_request.email_action.description')))
2
+
1
3
  - content_for :preheader_text do
2
4
  = @content
3
5
 
@@ -5,7 +5,8 @@
5
5
 
6
6
  .grid
7
7
  .grid__cell.grid__cell--25
8
- .product-summary{ itemscope: true, itemtype: 'http://schema.org/Product' }
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
@@ -67,4 +67,9 @@ module Workarea
67
67
  'admin.marketing_dashboard_navigation',
68
68
  'workarea/admin/reviews/dashboard_navigation'
69
69
  )
70
+
71
+ Workarea.append_partials(
72
+ 'admin.reports_dashboard',
73
+ 'workarea/admin/dashboards/reviews_by_product_card'
74
+ )
70
75
  end
@@ -1,5 +1,5 @@
1
1
  Workarea.configure do |config|
2
- config.seeds << 'Workarea::ReviewSeeds'
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
@@ -1,6 +1,8 @@
1
- Sidekiq::Cron::Job.create(
2
- name: 'Workarea::SendReviewRequests',
3
- klass: 'Workarea::SendReviewRequests',
4
- cron: "0 0 * * * #{Time.zone.tzinfo.identifier}",
5
- queue: 'low'
6
- )
1
+ unless Workarea.config.skip_service_connections
2
+ Sidekiq::Cron::Job.create(
3
+ name: 'Workarea::SendReviewRequests',
4
+ klass: 'Workarea::SendReviewRequests',
5
+ cron: "0 0 * * * #{Time.zone.tzinfo.identifier}",
6
+ queue: 'low'
7
+ )
8
+ end
@@ -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: