workarea-reviews 3.0.8
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 +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 .
|