workarea-reviews 3.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +20 -0
- data/.eslintignore +2 -0
- data/.eslintrc +24 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.gitignore +16 -0
- data/.scss-lint.yml +188 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +756 -0
- data/CODE_OF_CONDUCT.md +3 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +6 -0
- data/LICENSE +52 -0
- data/README.md +107 -0
- data/Rakefile +54 -0
- data/app/assets/images/workarea/admin/icons/star.svg +1 -0
- data/app/assets/images/workarea/storefront/icons/empty_star.svg +1 -0
- data/app/assets/images/workarea/storefront/icons/half_star.svg +1 -0
- data/app/assets/images/workarea/storefront/icons/star.svg +1 -0
- data/app/assets/javascripts/workarea/storefront/reviews/modules/product_review_ajax_submit.js +39 -0
- data/app/assets/javascripts/workarea/storefront/reviews/modules/product_reviews_sort_menus.js +82 -0
- data/app/assets/javascripts/workarea/storefront/reviews/modules/rating_buttons.js +103 -0
- data/app/assets/javascripts/workarea/storefront/reviews/templates/sort_by_property.jst.ejs +11 -0
- data/app/assets/stylesheets/workarea/storefront/reviews/components/_rating.scss +28 -0
- data/app/assets/stylesheets/workarea/storefront/reviews/components/_reviews.scss +59 -0
- data/app/assets/stylesheets/workarea/storefront/reviews/components/_reviews_aggregate.scss +38 -0
- data/app/assets/stylesheets/workarea/storefront/reviews/components/_write_review.scss +63 -0
- data/app/controllers/workarea/admin/catalog_products_controller.decorator +9 -0
- data/app/controllers/workarea/admin/import_reviews_controller.rb +35 -0
- data/app/controllers/workarea/admin/reviews_controller.rb +51 -0
- data/app/controllers/workarea/storefront/review_requests_controller.rb +50 -0
- data/app/controllers/workarea/storefront/reviews_controller.rb +53 -0
- data/app/helpers/workarea/admin/reviews_helper.rb +12 -0
- data/app/helpers/workarea/storefront/reviews_helper.rb +24 -0
- data/app/mailers/workarea/admin/status_report_mailer.decorator +8 -0
- data/app/mailers/workarea/storefront/review_mailer.rb +23 -0
- data/app/models/workarea/catalog/product.decorator +15 -0
- data/app/models/workarea/fulfillment.decorator +11 -0
- data/app/models/workarea/review/request.rb +62 -0
- data/app/models/workarea/review.rb +135 -0
- data/app/models/workarea/search/admin/review.rb +72 -0
- data/app/models/workarea/search/storefront/product.decorator +9 -0
- data/app/models/workarea/sort.decorator +17 -0
- data/app/queries/workarea/review_request_params.rb +47 -0
- data/app/queries/workarea/review_summary.rb +11 -0
- data/app/queries/workarea/search/admin_reviews.rb +40 -0
- data/app/queries/workarea/search/category_browse.decorator +9 -0
- data/app/queries/workarea/search/product_search.decorator +9 -0
- data/app/seeds/reviews.rb +38 -0
- data/app/services/workarea/create_review.rb +99 -0
- data/app/view_models/workarea/admin/product_view_model.decorator +7 -0
- data/app/view_models/workarea/admin/review_view_model.rb +23 -0
- data/app/view_models/workarea/admin/reviews_search_view_model.rb +46 -0
- data/app/view_models/workarea/storefront/product_view_model.decorator +13 -0
- data/app/view_models/workarea/storefront/review_view_model.rb +14 -0
- data/app/views/workarea/admin/activities/_review_create.html.haml +12 -0
- data/app/views/workarea/admin/activities/_review_destroy.html.haml +10 -0
- data/app/views/workarea/admin/activities/_review_update.html.haml +10 -0
- data/app/views/workarea/admin/reviews/_aux_navigation.html.haml +5 -0
- data/app/views/workarea/admin/reviews/_catalog_products_aux_link.html.haml +5 -0
- data/app/views/workarea/admin/reviews/_dashboard_navigation.html.haml +1 -0
- data/app/views/workarea/admin/reviews/_menu.html.haml +3 -0
- data/app/views/workarea/admin/reviews/_summary.html.haml +12 -0
- data/app/views/workarea/admin/reviews/edit.html.haml +77 -0
- data/app/views/workarea/admin/reviews/index.html.haml +74 -0
- data/app/views/workarea/admin/status_report_mailer/_reviews.html.haml +5 -0
- data/app/views/workarea/storefront/products/_rating.html.haml +10 -0
- data/app/views/workarea/storefront/products/_reviews.html.haml +23 -0
- data/app/views/workarea/storefront/products/_reviews_aggregate.html.haml +22 -0
- data/app/views/workarea/storefront/products/_reviews_summary.html.haml +2 -0
- data/app/views/workarea/storefront/review_mailer/review_request.html.haml +22 -0
- data/app/views/workarea/storefront/review_requests/show.html.haml +44 -0
- data/app/views/workarea/storefront/reviews/new.html.haml +68 -0
- data/app/views/workarea/storefront/style_guides/_reviews_product_summary_docs.html.haml +13 -0
- data/app/views/workarea/storefront/style_guides/components/_reviews.html.haml +17 -0
- data/app/workers/workarea/create_review_requests.rb +33 -0
- data/app/workers/workarea/schedule_review_requests.rb +44 -0
- data/app/workers/workarea/send_review_requests.rb +12 -0
- data/app/workers/workarea/update_product_review_data.rb +26 -0
- data/bin/rails +18 -0
- data/config/initializers/append_points.rb +70 -0
- data/config/initializers/assets.rb +1 -0
- data/config/initializers/configuration.rb +29 -0
- data/config/initializers/rack_attack.rb +11 -0
- data/config/initializers/recaptcha.rb +19 -0
- data/config/initializers/scheduled_jobs.rb +6 -0
- data/config/locales/en.yml +104 -0
- data/config/routes.rb +21 -0
- data/lib/tasks/verify.rake +16 -0
- data/lib/workarea/mailer_previews/storefront/review_mailer_preview.rb +10 -0
- data/lib/workarea/reviews/engine.rb +12 -0
- data/lib/workarea/reviews/version.rb +5 -0
- data/lib/workarea/reviews.rb +13 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/config/manifest.js +4 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/jobs/application_job.rb +2 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +34 -0
- data/test/dummy/bin/update +29 -0
- data/test/dummy/config/application.rb +25 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/cable.yml +9 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +55 -0
- data/test/dummy/config/environments/production.rb +86 -0
- data/test/dummy/config/environments/test.rb +43 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/new_framework_defaults.rb +21 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/workarea.rb +5 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/puma.rb +47 -0
- data/test/dummy/config/routes.rb +5 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/config.ru +5 -0
- data/test/dummy/db/seeds.rb +2 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/factories/reviews.rb +32 -0
- data/test/fixtures/reviews.csv +5 -0
- data/test/helpers/workarea/admin/reviews_helper_test.rb +17 -0
- data/test/helpers/workarea/storefront/reviews_helper_test.rb +19 -0
- data/test/integration/workarea/admin/reviews_integration_test.rb +33 -0
- data/test/integration/workarea/storefront/review_requests_integration_test.rb +63 -0
- data/test/integration/workarea/storefront/reviews_integration_test.rb +82 -0
- data/test/javascripts/fixtures/product_review_ajax_submit.html.haml +4 -0
- data/test/javascripts/product_review_ajax_submit_spec.js +28 -0
- data/test/javascripts/spec_helper.js +23 -0
- data/test/models/workarea/review/request_test.rb +18 -0
- data/test/models/workarea/review_test.rb +94 -0
- data/test/models/workarea/search/storefront/product_test.decorator +14 -0
- data/test/queries/workarea/review_request_params_test.rb +64 -0
- data/test/services/workarea/create_review_test.rb +131 -0
- data/test/system/workarea/admin/review_system_test.rb +96 -0
- data/test/system/workarea/storefront/review_request_system_test.rb +34 -0
- data/test/system/workarea/storefront/review_system_test.rb +113 -0
- data/test/teaspoon_env.rb +10 -0
- data/test/test_helper.rb +11 -0
- data/test/workers/workarea/create_review_requests_test.rb +38 -0
- data/test/workers/workarea/schedule_review_requests_test.rb +115 -0
- data/test/workers/workarea/send_review_requests_test.rb +30 -0
- data/workarea-reviews.gemspec +22 -0
- metadata +241 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
- @title = t('workarea.storefront.reviews.write_a_review_title', name: @product.browser_title)
|
2
|
+
|
3
|
+
.write-review.view
|
4
|
+
%h1= t('workarea.storefront.reviews.write_a_review_title', name: @product.name)
|
5
|
+
|
6
|
+
.grid
|
7
|
+
.grid__cell.grid__cell--25
|
8
|
+
.product-summary{ itemscope: true, itemtype: 'http://schema.org/Product' }
|
9
|
+
= render 'workarea/storefront/products/summary', product: @product
|
10
|
+
|
11
|
+
.grid__cell.grid__cell--75
|
12
|
+
- unless request.get?
|
13
|
+
- if @review.errors.any?
|
14
|
+
- @review.errors.full_messages.each do |message|
|
15
|
+
= render_message 'error', message, data: { analytics: validation_error_analytics_data('review').to_json }
|
16
|
+
|
17
|
+
= form_tag complete_review_request_path(@request.token), method: 'post', id: 'review_request_form' do
|
18
|
+
.property
|
19
|
+
= label_tag 'review[rating]', nil, class: 'property__name' do
|
20
|
+
%span.property__text= t('workarea.storefront.reviews.rating')
|
21
|
+
%ul.write-review__ratings
|
22
|
+
- (1..5).each do |i|
|
23
|
+
%li.write-review__rating
|
24
|
+
= radio_button_tag 'review[rating]', i, (i == @review.rating || i == 5) ? true : false, class: 'write-review__rating-input', data: { rating_button: '' }
|
25
|
+
= label_tag "review[rating][#{i}]", nil, class: 'write-review__star' do
|
26
|
+
= inline_svg 'workarea/storefront/icons/star.svg', class: 'write-review__star-icon', title: t('workarea.storefront.reviews.stars', count: i)
|
27
|
+
|
28
|
+
.property
|
29
|
+
= label_tag 'review[title]', nil, class: 'property__name' do
|
30
|
+
%span.property__text= t('workarea.storefront.reviews.review_title')
|
31
|
+
%span.property__requirement.property__requirement--optional= t('workarea.storefront.forms.optional')
|
32
|
+
.value= text_field_tag 'review[title]', @review.title, class: 'text-box'
|
33
|
+
|
34
|
+
.property
|
35
|
+
= label_tag 'review[body]', nil, class: 'property__name' do
|
36
|
+
%span.property__requirement.property__requirement--required= t('workarea.storefront.forms.required')
|
37
|
+
%span.property__text= t('workarea.storefront.reviews.your_review')
|
38
|
+
.value= text_area_tag 'review[body]', @review.body, class: 'text-box text-box--multi-line', required: true
|
39
|
+
|
40
|
+
.property= recaptcha_tags
|
41
|
+
|
42
|
+
.grid.grid--auto
|
43
|
+
%p.grid__cell= button_tag t('workarea.storefront.reviews.submit'), value: 'submit_review', class: 'button', data: { disable_with: loading_indicator(t('workarea.storefront.reviews.submit_disabled')) }
|
44
|
+
%p.grid__cell= link_to t('workarea.storefront.forms.cancel'), product_path(@product), class: 'text-button', data: { dialog_close_button: '' }
|
@@ -0,0 +1,68 @@
|
|
1
|
+
- @title = t('workarea.storefront.reviews.write_a_review_title', name: @product.browser_title)
|
2
|
+
|
3
|
+
.write-review.view
|
4
|
+
%h1= t('workarea.storefront.reviews.write_a_review_title', name: @product.browser_title)
|
5
|
+
|
6
|
+
- unless request.get?
|
7
|
+
- if @create_review.errors.any?
|
8
|
+
- @create_review.errors.full_messages.each do |message|
|
9
|
+
= render_message 'error', message, data: { analytics: validation_error_analytics_data('review').to_json }
|
10
|
+
|
11
|
+
= form_tag create_product_review_path(@product), method: 'post', id: 'review_form', data: { product_review_ajax_submit: '' } do
|
12
|
+
= hidden_field_tag 'review[product_id]', @product.id
|
13
|
+
|
14
|
+
- if display_purchase_requirement_message
|
15
|
+
%span.write-review__purchase-requirement= t('workarea.storefront.reviews.purchase_requirement')
|
16
|
+
|
17
|
+
- if @review.requires_public_info?
|
18
|
+
.property
|
19
|
+
= label_tag 'review[first_name]', nil, class: 'property__name' do
|
20
|
+
%span.property__requirement.property__requirement--required= t('workarea.storefront.forms.required')
|
21
|
+
%span.property__text= t('workarea.storefront.users.first_name')
|
22
|
+
.value
|
23
|
+
= text_field_tag 'review[first_name]', @review.first_name, class: 'text-box', required: true
|
24
|
+
.value__note= t('workarea.storefront.reviews.notes.display_name')
|
25
|
+
.property
|
26
|
+
= label_tag 'review[last_name]', nil, class: 'property__name' do
|
27
|
+
%span.property__requirement.property__requirement--required= t('workarea.storefront.forms.required')
|
28
|
+
%span.property__text= t('workarea.storefront.users.last_name')
|
29
|
+
.value
|
30
|
+
= text_field_tag 'review[last_name]', @review.last_name, class: 'text-box', required: true
|
31
|
+
.value__note= t('workarea.storefront.reviews.notes.display_name')
|
32
|
+
|
33
|
+
- if @review.anonymous?
|
34
|
+
.property
|
35
|
+
= label_tag 'review[email]', nil, class: 'property__name' do
|
36
|
+
%span.property__text= t('workarea.storefront.users.email')
|
37
|
+
%span.property__requirement.property__requirement--optional= t('workarea.storefront.forms.optional')
|
38
|
+
.value
|
39
|
+
= email_field_tag 'review[email]', @review.email, class: 'text-box', autocapitalize: 'off', autocorrect: 'off', autocomplete: 'email'
|
40
|
+
.value__note= t('workarea.storefront.reviews.notes.email')
|
41
|
+
|
42
|
+
.property
|
43
|
+
= label_tag 'review[rating]', nil, class: 'property__name' do
|
44
|
+
%span.property__text= t('workarea.storefront.reviews.rating')
|
45
|
+
%ul.write-review__ratings
|
46
|
+
- (1..5).each do |i|
|
47
|
+
%li.write-review__rating
|
48
|
+
= radio_button_tag 'review[rating]', i, (i == @review.rating || i == 5) ? true : false, class: 'write-review__rating-input', data: { rating_button: '' }
|
49
|
+
= label_tag "review[rating][#{i}]", nil, class: 'write-review__star' do
|
50
|
+
= inline_svg 'workarea/storefront/icons/star.svg', class: 'write-review__star-icon', title: t('workarea.storefront.reviews.stars', count: i)
|
51
|
+
|
52
|
+
.property
|
53
|
+
= label_tag 'review[title]', nil, class: 'property__name' do
|
54
|
+
%span.property__text= t('workarea.storefront.reviews.review_title')
|
55
|
+
%span.property__requirement.property__requirement--optional= t('workarea.storefront.forms.optional')
|
56
|
+
.value= text_field_tag 'review[title]', @review.title, class: 'text-box'
|
57
|
+
|
58
|
+
.property
|
59
|
+
= label_tag 'review[body]', nil, class: 'property__name' do
|
60
|
+
%span.property__requirement.property__requirement--required= t('workarea.storefront.forms.required')
|
61
|
+
%span.property__text= t('workarea.storefront.reviews.your_review')
|
62
|
+
.value= text_area_tag 'review[body]', @review.body, class: 'text-box text-box--multi-line', required: true
|
63
|
+
|
64
|
+
.property= recaptcha_tags
|
65
|
+
|
66
|
+
.grid.grid--auto
|
67
|
+
%p.grid__cell= button_tag t('workarea.storefront.reviews.submit'), value: 'submit_review', class: 'button', data: { disable_with: loading_indicator(t('workarea.storefront.reviews.submit_disabled')) }
|
68
|
+
%p.grid__cell= link_to t('workarea.storefront.forms.cancel'), product_path(@product), class: 'text-button', data: { dialog_close_button: '' }
|
@@ -0,0 +1,13 @@
|
|
1
|
+
%p With reviews:
|
2
|
+
.style-guide__example-block
|
3
|
+
.product-summary
|
4
|
+
%p.product-summary__media
|
5
|
+
= link_to('#', class: 'product-summary__media-link') do
|
6
|
+
= image_tag('workarea/storefront/style_guide_product.jpg', alt: 'Product Name', class: 'product-summary__media-image')
|
7
|
+
.product-summary__info
|
8
|
+
%p.product-summary__name= link_to('Product Name', '#')
|
9
|
+
.product-summary__price-group
|
10
|
+
%p.product-summary__price
|
11
|
+
%span= number_to_currency(109.99)
|
12
|
+
= rating_stars(3.5, aggregate: true)
|
13
|
+
%p.product-summary__quickview= link_to('Quick View', '#', class: 'button button--small', rel: 'nofollow', data: { dialog_button: '' })
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#rating.style-guide__subsection
|
2
|
+
|
3
|
+
%h3= link_to_style_guide('components', 'rating')
|
4
|
+
|
5
|
+
%p Displays a graphical rating scale for a numerical rating (1.0 - 5.0). Use the <code>rating_stars(rating, options = {})</code> helper to generate the rating stars.
|
6
|
+
|
7
|
+
%h4 Options
|
8
|
+
%ul
|
9
|
+
%li
|
10
|
+
<code>aggregate</code> (default: false)
|
11
|
+
%ul
|
12
|
+
%li Passing <code>aggregate: true</code> will change schema.org rating type from `reviewRating` to `aggregateRating` (an average rating from multiple reviews). See <a href="https://schema.org/AggregateRating">https://schema.org/AggregateRating</a> for more info.
|
13
|
+
|
14
|
+
- [1.5, 2.0, 3.75, 4.1, 4.8].each do |rating|
|
15
|
+
%p Example: rating of #{rating}
|
16
|
+
.style-guide__example-block
|
17
|
+
= rating_stars(rating, aggregate: true)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Workarea
|
2
|
+
class CreateReviewRequests
|
3
|
+
include Sidekiq::Worker
|
4
|
+
include Sidekiq::CallbacksWorker
|
5
|
+
|
6
|
+
sidekiq_options(
|
7
|
+
enqueue_on: {
|
8
|
+
Order => :place,
|
9
|
+
only_if: -> { Workarea.config.send_transactional_emails }
|
10
|
+
},
|
11
|
+
queue: 'low'
|
12
|
+
)
|
13
|
+
|
14
|
+
def perform(order_id)
|
15
|
+
order = Order.find(order_id)
|
16
|
+
product_ids = order.items
|
17
|
+
.sort_by(&:original_unit_price)
|
18
|
+
.reverse
|
19
|
+
.map(&:product_id)
|
20
|
+
.uniq
|
21
|
+
.take(Workarea.config.review_requests_per_order)
|
22
|
+
|
23
|
+
product_ids.each do |product_id|
|
24
|
+
Review::Request.create(
|
25
|
+
email: order.email,
|
26
|
+
order_id: order.id,
|
27
|
+
user_id: order.user_id,
|
28
|
+
product_id: product_id
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Workarea
|
2
|
+
class ScheduleReviewRequests
|
3
|
+
include Sidekiq::Worker
|
4
|
+
|
5
|
+
sidekiq_options queue: 'low'
|
6
|
+
|
7
|
+
def perform(order_id, shipped_items)
|
8
|
+
requests = Review::Request.by_order(order_id).to_a
|
9
|
+
return if requests.all?(&:send_after)
|
10
|
+
|
11
|
+
order = Order.find(order_id)
|
12
|
+
product_ids =
|
13
|
+
shipped_items.map do |item|
|
14
|
+
order_item = order.items.detect { |i| i.id.to_s == item['id'] }
|
15
|
+
order_item&.product_id
|
16
|
+
end
|
17
|
+
.compact
|
18
|
+
|
19
|
+
schedule_requests(
|
20
|
+
requests.select { |request| request.product_id.in?(product_ids) },
|
21
|
+
requests
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def schedule_requests(affected_requests, all_requests)
|
26
|
+
affected_requests.each do |request|
|
27
|
+
next if request.send_after.present?
|
28
|
+
|
29
|
+
last_scheduled =
|
30
|
+
all_requests.select(&:send_after).sort_by(&:send_after).last
|
31
|
+
|
32
|
+
date_to_send =
|
33
|
+
if last_scheduled.present?
|
34
|
+
last_scheduled.send_after +
|
35
|
+
Workarea.config.review_request_secondary_delivery_delay
|
36
|
+
else
|
37
|
+
Workarea.config.review_request_initial_delivery_delay.from_now
|
38
|
+
end
|
39
|
+
|
40
|
+
request.update_attributes(send_after: date_to_send)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Workarea
|
2
|
+
class UpdateProductReviewData
|
3
|
+
include Sidekiq::Worker
|
4
|
+
include Sidekiq::CallbacksWorker
|
5
|
+
|
6
|
+
sidekiq_options(
|
7
|
+
enqueue_on: {
|
8
|
+
Workarea::Review => [:save, :destroy],
|
9
|
+
with: -> { [product_id] }
|
10
|
+
},
|
11
|
+
unique: :until_and_while_executing
|
12
|
+
)
|
13
|
+
|
14
|
+
def perform(product_id)
|
15
|
+
product = Workarea::Catalog::Product.find(product_id)
|
16
|
+
review_data = Review.find_single_aggregates(product_id)
|
17
|
+
|
18
|
+
product.update_attributes!(
|
19
|
+
total_reviews: review_data[:count],
|
20
|
+
average_rating: review_data[:average]
|
21
|
+
)
|
22
|
+
rescue Mongoid::Errors::DocumentNotFound
|
23
|
+
# we're ok if the product doesn't exist
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/bin/rails
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
3
|
+
# installed from the root of your application.
|
4
|
+
|
5
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
6
|
+
ENGINE_PATH = File.expand_path('../../lib/workarea/reviews/engine', __FILE__)
|
7
|
+
|
8
|
+
# Set up gems listed in the Gemfile.
|
9
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
10
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
11
|
+
|
12
|
+
require 'action_controller/railtie'
|
13
|
+
require 'action_view/railtie'
|
14
|
+
require 'action_mailer/railtie'
|
15
|
+
require 'rails/test_unit/railtie'
|
16
|
+
require 'sprockets/railtie'
|
17
|
+
|
18
|
+
require 'rails/engine/commands'
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Workarea
|
2
|
+
#
|
3
|
+
# Storefront
|
4
|
+
#
|
5
|
+
#
|
6
|
+
|
7
|
+
Plugin.append_stylesheets(
|
8
|
+
'storefront.components',
|
9
|
+
'workarea/storefront/reviews/components/rating',
|
10
|
+
'workarea/storefront/reviews/components/reviews',
|
11
|
+
'workarea/storefront/reviews/components/reviews_aggregate',
|
12
|
+
'workarea/storefront/reviews/components/write_review',
|
13
|
+
)
|
14
|
+
|
15
|
+
Plugin.append_javascripts(
|
16
|
+
'storefront.templates',
|
17
|
+
'workarea/storefront/reviews/templates/sort_by_property'
|
18
|
+
)
|
19
|
+
|
20
|
+
Plugin.append_javascripts(
|
21
|
+
'storefront.modules',
|
22
|
+
'workarea/storefront/reviews/modules/product_review_ajax_submit',
|
23
|
+
'workarea/storefront/reviews/modules/product_reviews_sort_menus',
|
24
|
+
'workarea/storefront/reviews/modules/rating_buttons',
|
25
|
+
)
|
26
|
+
|
27
|
+
Plugin.append_partials(
|
28
|
+
'storefront.style_guide_product_summary',
|
29
|
+
'workarea/storefront/style_guides/reviews_product_summary_docs'
|
30
|
+
)
|
31
|
+
|
32
|
+
Plugin.append_partials(
|
33
|
+
'storefront.product_details',
|
34
|
+
'workarea/storefront/products/reviews_aggregate'
|
35
|
+
)
|
36
|
+
|
37
|
+
Plugin.append_partials(
|
38
|
+
'storefront.product_show',
|
39
|
+
'workarea/storefront/products/reviews'
|
40
|
+
)
|
41
|
+
|
42
|
+
Plugin.append_partials(
|
43
|
+
'storefront.product_summary',
|
44
|
+
'workarea/storefront/products/reviews_summary'
|
45
|
+
)
|
46
|
+
|
47
|
+
#
|
48
|
+
# Admin
|
49
|
+
#
|
50
|
+
#
|
51
|
+
Plugin.append_partials(
|
52
|
+
'admin.marketing_menu',
|
53
|
+
'workarea/admin/reviews/menu'
|
54
|
+
)
|
55
|
+
|
56
|
+
Plugin.append_partials(
|
57
|
+
'admin.catalog_product_aux_navigation',
|
58
|
+
'workarea/admin/reviews/catalog_products_aux_link'
|
59
|
+
)
|
60
|
+
|
61
|
+
Plugin.append_partials(
|
62
|
+
'admin.status_report_mailer',
|
63
|
+
'workarea/admin/status_report_mailer/reviews'
|
64
|
+
)
|
65
|
+
|
66
|
+
Workarea.append_partials(
|
67
|
+
'admin.marketing_dashboard_navigation',
|
68
|
+
'workarea/admin/reviews/dashboard_navigation'
|
69
|
+
)
|
70
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Rails.application.config.assets.precompile += %w( workarea/**/icons/*.svg )
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Workarea.configure do |config|
|
2
|
+
config.seeds << 'Workarea::ReviewSeeds'
|
3
|
+
|
4
|
+
config.jump_to_navigation.merge!('Product Reviews' => :reviews_path)
|
5
|
+
|
6
|
+
config.product_copy_default_attributes ||= {}
|
7
|
+
config.product_copy_default_attributes.merge!(
|
8
|
+
total_reviews: 0,
|
9
|
+
average_rating: nil
|
10
|
+
)
|
11
|
+
|
12
|
+
config.data_file_ignored_fields << %w(total_reviews average_rating)
|
13
|
+
|
14
|
+
# The amount of time before a Review::Request will auto expire
|
15
|
+
# and be removed from the collection
|
16
|
+
config.review_request_ttl = 6.months
|
17
|
+
|
18
|
+
# The maximum number of review requests to send for each order
|
19
|
+
# Each request will be for a different order item, selected by
|
20
|
+
# most expensive.
|
21
|
+
config.review_requests_per_order = 3
|
22
|
+
|
23
|
+
# The amount of time after an order is shipped before the first review
|
24
|
+
# requests will be sent.
|
25
|
+
config.review_request_initial_delivery_delay = 14.days
|
26
|
+
|
27
|
+
# The amount of time after the last request was sent to send another.
|
28
|
+
config.review_request_secondary_delivery_delay = 5.days
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Rack::Attack
|
2
|
+
# Throttle POST requests to /reviews by email address
|
3
|
+
#
|
4
|
+
# Key: "rack::attack:#{Time.now.to_i/:period}:reviews/email:#{req.email}"
|
5
|
+
throttle('reviews/email', limit: 2, period: 2.minutes) do |req|
|
6
|
+
if req.path == '/reviews' && req.post?
|
7
|
+
# return the email if present, nil otherwise
|
8
|
+
req.params['email'].presence
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
recaptcha =
|
2
|
+
if Rails.application.secrets.recaptcha.present?
|
3
|
+
Rails.application.secrets.recaptcha.symbolize_keys
|
4
|
+
elsif Rails.env.development? || Rails.env.test?
|
5
|
+
# These are keys provided by google for the purpose of testing.
|
6
|
+
# See https://developers.google.com/recaptcha/docs/faq
|
7
|
+
{
|
8
|
+
site_key: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI',
|
9
|
+
secret_key: '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
if recaptcha.present?
|
14
|
+
Recaptcha.configure do |config|
|
15
|
+
config.site_key = recaptcha[:site_key]
|
16
|
+
config.secret_key = recaptcha[:secret_key]
|
17
|
+
config.proxy = ENV.fetch('HTTP_PROXY', (recaptcha[:proxy] if recaptcha[:proxy].present?))
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
en:
|
2
|
+
workarea:
|
3
|
+
admin:
|
4
|
+
activities:
|
5
|
+
review: Review
|
6
|
+
review_create_html: Created a %{name}
|
7
|
+
review_destroy_html: Removed a %{name}
|
8
|
+
review_update_html: updated %{fields} on the %{name} review
|
9
|
+
catalog:
|
10
|
+
dashboard_link: Dashboard link
|
11
|
+
catalog_products:
|
12
|
+
reviews_link: All Reviews
|
13
|
+
pending: (%{pending} Pending)
|
14
|
+
dashboards:
|
15
|
+
marketing:
|
16
|
+
reviews: Product reviews
|
17
|
+
fields:
|
18
|
+
body: Body
|
19
|
+
title: Title
|
20
|
+
rating: Rating
|
21
|
+
approved: Approved
|
22
|
+
auto_approve: Approve All Reviews
|
23
|
+
verified: Verified Purchaser
|
24
|
+
reviews:
|
25
|
+
approved: Approved
|
26
|
+
catalog_products_cards:
|
27
|
+
title: Reviews
|
28
|
+
edit:
|
29
|
+
review: Review
|
30
|
+
review_for_html: Review for %{product_link}
|
31
|
+
save_review: Save Review
|
32
|
+
title: Title
|
33
|
+
view_product: View Product
|
34
|
+
view_user: View User
|
35
|
+
flash_messages:
|
36
|
+
destroyed: Review has been removed.
|
37
|
+
updated: Review has been saved
|
38
|
+
index:
|
39
|
+
dashboard_link: Marketing dashboard
|
40
|
+
heading: Reviews
|
41
|
+
page_title: Reviews
|
42
|
+
review: Review
|
43
|
+
review_for: Review for %{product}
|
44
|
+
menu:
|
45
|
+
product_reviews:
|
46
|
+
one: Product Reviews (%{count})
|
47
|
+
other: Product Reviews (%{count})
|
48
|
+
zero: Product Reviews
|
49
|
+
pending: Pending
|
50
|
+
summary:
|
51
|
+
type: Review
|
52
|
+
unverified: Unverified
|
53
|
+
verified: Verified
|
54
|
+
status_report_mailer:
|
55
|
+
reviews:
|
56
|
+
pending_html: "%{count} awaiting approval."
|
57
|
+
review: review
|
58
|
+
recent_html: "%{count} submitted within the last day."
|
59
|
+
title: Reviews
|
60
|
+
sorts:
|
61
|
+
top_rated: Top Rated
|
62
|
+
highest_rating: Highest Rating
|
63
|
+
lowest_rating: Lowest Rating
|
64
|
+
storefront:
|
65
|
+
email:
|
66
|
+
review_request:
|
67
|
+
subject: Review your recent purchase of %{product}
|
68
|
+
link: Write a review
|
69
|
+
fields:
|
70
|
+
username: Username
|
71
|
+
review_requests:
|
72
|
+
flash_messages:
|
73
|
+
already_submitted: This review has already been submitted.
|
74
|
+
reviews:
|
75
|
+
flash_messages:
|
76
|
+
created: Your review has been submitted. Thanks!
|
77
|
+
failure: There was a problem saving your review.
|
78
|
+
heading: Reviews
|
79
|
+
out_of: out of
|
80
|
+
purchase_requirement: A purchase is required before you can post a review
|
81
|
+
rating: Rating
|
82
|
+
read_reviews: Read Reviews
|
83
|
+
review_for: Review for %{product}
|
84
|
+
review_title: Review Title
|
85
|
+
reviews:
|
86
|
+
one: "%{count} review"
|
87
|
+
other: "%{count} reviews"
|
88
|
+
zero: "%{count} reviews"
|
89
|
+
stars:
|
90
|
+
one: 1 star
|
91
|
+
other: "%{count} stars"
|
92
|
+
submit: Submit Review
|
93
|
+
submit_disabled: Submitting Review...
|
94
|
+
total_spent_validation: must make a purchase before creating a review
|
95
|
+
verified_purchaser: Verified Purchaser
|
96
|
+
write_a_review: Write a Review
|
97
|
+
write_a_review_title: Write a Review for "%{name}"
|
98
|
+
your_review: Your Review
|
99
|
+
notes:
|
100
|
+
display_name: Only the first letter of your name will be displayed.
|
101
|
+
email: Will only be used if we need to get in touch with you.
|
102
|
+
users:
|
103
|
+
first_name: First name
|
104
|
+
last_name: Last name
|
data/config/routes.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Workarea::Admin::Engine.routes.draw do
|
2
|
+
scope '(:locale)', constraints: Workarea::I18n.routes_constraint do
|
3
|
+
resources :reviews, except: [:new, :create]
|
4
|
+
resources :catalog_products, only: [] do
|
5
|
+
member do
|
6
|
+
get :reviews
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
Workarea::Storefront::Engine.routes.draw do
|
13
|
+
scope '(:locale)', constraints: Workarea::I18n.routes_constraint do
|
14
|
+
get 'reviews/:product_id/new' => 'reviews#new', as: :new_product_review
|
15
|
+
post 'reviews/:product_id' => 'reviews#create', as: :create_product_review
|
16
|
+
|
17
|
+
resources :review_requests, only: :show do
|
18
|
+
member { post :complete }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
namespace :workarea do
|
2
|
+
namespace :reviews do
|
3
|
+
desc 'Assign verified to each review based on whether the user has any orders with the product being reviewed'
|
4
|
+
task verify: :environment do
|
5
|
+
Workarea::Review.all.each_by(100) do |review|
|
6
|
+
user = Workarea::User.find(review.user_id) rescue nil
|
7
|
+
|
8
|
+
next unless user.present?
|
9
|
+
|
10
|
+
creator = Workarea::CreateReview.new(review, user: user)
|
11
|
+
|
12
|
+
review.update!(verified: creator.verified?)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/test/dummy/Rakefile
ADDED
File without changes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|