workarea-wish_lists 3.0.5 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.json +35 -0
  3. data/.github/workflows/ci.yml +60 -0
  4. data/.rubocop.yml +3 -0
  5. data/.stylelintrc.json +8 -0
  6. data/CHANGELOG.md +0 -51
  7. data/Gemfile +3 -2
  8. data/Rakefile +5 -4
  9. data/app/assets/javascripts/workarea/storefront/wish_lists/modules/wish_list_analytics.js +31 -0
  10. data/app/controllers/workarea/admin/reports_controller.decorator +10 -0
  11. data/app/controllers/workarea/storefront/analytics_controller.decorator +17 -0
  12. data/app/controllers/workarea/storefront/users/wish_lists_controller.rb +7 -6
  13. data/app/helpers/workarea/storefront/wish_list_analytics_helper.rb +21 -0
  14. data/app/models/workarea/insights/most_wish_listed_products.rb +32 -0
  15. data/app/models/workarea/metrics/product_by_day.decorator +10 -0
  16. data/app/models/workarea/wish_list.rb +1 -2
  17. data/app/models/workarea/wish_list/item.rb +1 -2
  18. data/app/queries/workarea/order_metrics.decorator +24 -0
  19. data/app/queries/workarea/reports/wish_list_products.rb +51 -0
  20. data/app/seeds/workarea/wish_list_seeds.rb +11 -0
  21. data/app/services/workarea/wish_list_session.rb +11 -11
  22. data/app/view_models/workarea/admin/dashboards/reports_view_model.decorator +10 -0
  23. data/app/view_models/workarea/admin/reports/wish_list_products_view_model.rb +20 -0
  24. data/app/views/workarea/admin/dashboards/_wish_list_products_card.html.haml +17 -0
  25. data/app/views/workarea/admin/insights/_most_wish_listed_products.html.haml +22 -0
  26. data/app/views/workarea/admin/reports/wish_list_products.html.haml +38 -0
  27. data/app/views/workarea/storefront/carts/_move_to_wish_list.html.haml +1 -1
  28. data/app/views/workarea/storefront/pages/_wish_lists_robots.text.erb +1 -0
  29. data/app/views/workarea/storefront/products/_add_to_wish_list.html.haml +1 -1
  30. data/app/views/workarea/storefront/users/accounts/_wish_list_summary.html.haml +3 -1
  31. data/app/views/workarea/storefront/users/wish_lists/show.html.haml +9 -4
  32. data/app/views/workarea/storefront/wish_lists/show.html.haml +8 -3
  33. data/app/workers/workarea/save_order_metrics.decorator +21 -0
  34. data/config/initializers/append_points.rb +12 -1
  35. data/config/locales/en.yml +20 -0
  36. data/config/routes.rb +7 -0
  37. data/lib/workarea/wish_lists/engine.rb +6 -0
  38. data/lib/workarea/wish_lists/version.rb +1 -1
  39. data/package.json +9 -0
  40. data/test/dummy/config/initializers/session_store.rb +3 -1
  41. data/test/integration/workarea/storefront/wish_lists_integration_test.rb +2 -2
  42. data/test/models/workarea/insights/most_wish_listed_products_test.rb +63 -0
  43. data/test/queries/workarea/reports/wish_list_products_test.rb +122 -0
  44. data/test/queries/workarea/wish_list_order_metrics_test.rb +49 -0
  45. data/test/system/workarea/admin/wish_list_products_report_system_test.rb +44 -0
  46. data/test/system/workarea/storefront/wish_list_analytics_system_test.rb +75 -0
  47. data/test/view_models/workarea/storefront/{wish_list_user_view_model_test.rb → user_view_model_test.rb} +0 -0
  48. data/workarea-wish_lists.gemspec +4 -4
  49. metadata +35 -9
@@ -13,9 +13,20 @@ module Workarea
13
13
  next unless sku
14
14
  wish_list.add_item(product.id, sku, quantity)
15
15
 
16
+ Metrics::ProductByDay.inc(
17
+ key: { product_id: product.id },
18
+ wish_list_adds: 1
19
+ )
20
+
16
21
  if rand(2).zero?
17
22
  purchased_quantity = rand(quantity) + 1
18
23
  wish_list.mark_item_purchased(sku, purchased_quantity)
24
+
25
+ Metrics::ProductByDay.inc(
26
+ key: { product_id: product.id },
27
+ wish_list_units_sold: purchased_quantity,
28
+ wish_list_revenue: rand(10000) / 100.0
29
+ )
19
30
  end
20
31
  end
21
32
  end
@@ -2,11 +2,11 @@ module Workarea
2
2
  # WishListSession is responsible for storing a representation of a product that will later be added to a user's Wish List.
3
3
  #
4
4
  class WishListSession
5
- attr_reader :session
5
+ attr_reader :cookies
6
6
 
7
- # @param [Hash] user's Session
8
- def initialize(session)
9
- @session = session
7
+ # @param [Hash] user's cookies
8
+ def initialize(cookies)
9
+ @cookies = cookies
10
10
  end
11
11
 
12
12
  # Store a form-posted representation of a Wish List Item. This would include the product_id, sku and quantity of that item.
@@ -16,20 +16,20 @@ module Workarea
16
16
  # @return [void]
17
17
  def store_item(params)
18
18
  collection_keys.each do |key|
19
- session["wish_list_#{key}"] = params[key] if params[key].present?
19
+ cookies["wish_list_#{key}"] = params[key] if params[key].present?
20
20
  end
21
21
  end
22
22
 
23
- # Add the current item in the session to a the given wish list.
23
+ # Add the current item in the cookies to a the given wish list.
24
24
  #
25
25
  # @param [WishList]
26
26
  #
27
27
  # @return [void]
28
28
  def add_item(wish_list)
29
- product_id = session['wish_list_product_id']
30
- sku = session['wish_list_sku']
31
- quantity = session['wish_list_quantity']
32
- customizations = session['wish_list_customizations']
29
+ product_id = cookies['wish_list_product_id']
30
+ sku = cookies['wish_list_sku']
31
+ quantity = cookies['wish_list_quantity']
32
+ customizations = cookies['wish_list_customizations']
33
33
 
34
34
  wish_list.add_item(
35
35
  product_id,
@@ -40,7 +40,7 @@ module Workarea
40
40
  )
41
41
 
42
42
  collection_keys.each do |key|
43
- session.delete("wish_list_#{key}")
43
+ cookies.delete("wish_list_#{key}")
44
44
  end
45
45
  end
46
46
 
@@ -0,0 +1,10 @@
1
+ module Workarea
2
+ decorate Admin::Dashboards::ReportsViewModel, with: :wish_lists do
3
+ def wish_list_products
4
+ @wish_list_products ||= Admin::Reports::WishListProductsViewModel.wrap(
5
+ Workarea::Reports::WishListProducts.new(options),
6
+ options
7
+ )
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module Workarea
2
+ module Admin
3
+ module Reports
4
+ class WishListProductsViewModel < ApplicationViewModel
5
+ def results
6
+ @results ||= model.results.map do |result|
7
+ product = products.detect { |p| p.id == result['_id'] }
8
+ OpenStruct.new({ product: product }.merge(result))
9
+ end
10
+ end
11
+
12
+ def products
13
+ @products ||= Catalog::Product.any_in(
14
+ id: model.results.map { |r| r['_id'] }
15
+ ).to_a
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ .grid__cell
2
+ .card{ class: card_classes(:wish_list_products_report, local_assigns[:active]) }
3
+ = link_to wish_list_products_report_path, class: 'card__header' do
4
+ %span.card__header-text= t('workarea.admin.reports.wish_list_products.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.wish_list_products.results.take(4).each do |result|
12
+ %tr
13
+ %td= result.product.present? ? result.product.name : result._id
14
+ %td.align-right= number_to_currency(result.revenue)
15
+
16
+ = link_to wish_list_products_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_wish_listed_products.title')
5
+ .insight__body
6
+ %p.insight__note= t('workarea.admin.insights.most_wish_listed_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_wish_listed_products.times', count: result.adds)
@@ -0,0 +1,38 @@
1
+ - @page_title = t('workarea.admin.reports.wish_list_products.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.wish_list_products.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 wish_list_products_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__count
16
+ = render_reports_results_message(@report)
17
+ = render 'workarea/admin/reports/export', report: @report
18
+
19
+ %table
20
+ %thead
21
+ %tr
22
+ %th= t('workarea.admin.fields.product')
23
+ %th.align-center= link_to_reports_sorting t('workarea.admin.fields.adds'), report: @report, sort_by: 'adds'
24
+ %th.align-center= link_to_reports_sorting t('workarea.admin.fields.deletes'), report: @report, sort_by: 'deletes'
25
+ %th.align-center= link_to_reports_sorting t('workarea.admin.fields.units_sold'), report: @report, sort_by: 'units_sold'
26
+ %th.align-right= link_to_reports_sorting t('workarea.admin.fields.revenue'), report: @report, sort_by: 'revenue'
27
+ %tbody
28
+ - @report.results.each do |result|
29
+ %tr
30
+ %td
31
+ - if result.product.present?
32
+ = link_to result.product.name, catalog_product_path(result.product)
33
+ - else
34
+ = result._id
35
+ %td.align-center= number_with_delimiter result.adds
36
+ %td.align-center= number_with_delimiter result.deletes
37
+ %td.align-center= number_with_delimiter result.units_sold
38
+ %td.align-right= number_to_currency(result.revenue)
@@ -1,4 +1,4 @@
1
1
  .grid__cell
2
2
  = form_tag from_cart_users_wish_list_path(item), method: 'post' do
3
3
  = hidden_field_tag :return_to, users_wish_list_path, id: "wishlist_return_to_#{dom_id(item)}"
4
- %p= button_tag t('workarea.storefront.wish_lists.move_to_wish_list'), value: 'move_to_wish_list', class: 'text-button'
4
+ %p= button_tag t('workarea.storefront.wish_lists.move_to_wish_list'), value: 'move_to_wish_list', class: 'text-button', data: { analytics: add_to_wish_list_analytics_data(item.product).to_json }
@@ -1,4 +1,4 @@
1
1
  .wish-list-button
2
2
  - if product.purchasable?
3
- = link_to users_wish_list_path, class: 'wish-list-button__link text-button', data: { wish_list_button: '' } do
3
+ = link_to users_wish_list_path, class: 'wish-list-button__link text-button', data: { wish_list_button: '', analytics: add_to_wish_list_analytics_data(product).to_json } do
4
4
  = t('workarea.storefront.wish_lists.add_to_wish_list')
@@ -8,8 +8,10 @@
8
8
  - if user.wish_list_items.any?
9
9
  .grid
10
10
  - user.wish_list_items.each do |item|
11
+ = render_schema_org(product_schema(item.product))
12
+
11
13
  .grid__cell.grid__cell--50.grid__cell--25-at-medium.grid__cell--20-at-wide
12
- .product-summary.product-summary--small{ itemscope: true, itemtype: 'http://schema.org/Product' }
14
+ .product-summary.product-summary--small
13
15
  = render 'workarea/storefront/products/summary', product: item.product
14
16
  - else
15
17
  %p= t('workarea.storefront.wish_lists.empty.message', state: t('workarea.storefront.wish_lists.empty.states.unpurchased'))
@@ -1,12 +1,14 @@
1
1
  - @title = t('workarea.storefront.wish_lists.wish_list_title')
2
2
 
3
+ = render_schema_org(breadcrumb_list_schema([[t('workarea.storefront.layouts.home'), root_url], [t('workarea.storefront.users.account'), users_account_url], [t('workarea.storefront.wish_lists.wish_list_title'), wish_lists_url]]))
4
+
3
5
  - content_for :breadcrumbs do
4
6
  %p.breadcrumbs__node-group
5
- %span.breadcrumbs__node{ itemprop: 'breadcrumb' }
7
+ %span.breadcrumbs__node
6
8
  = link_to t('workarea.storefront.layouts.home'), root_path, rel: 'home'
7
- %span.breadcrumbs__node{ itemprop: 'breadcrumb' }
9
+ %span.breadcrumbs__node
8
10
  = link_to t('workarea.storefront.users.account'), users_account_path
9
- %span.breadcrumbs__node{ itemprop: 'breadcrumb' }= t('workarea.storefront.wish_lists.wish_list_title')
11
+ %span.breadcrumbs__node= t('workarea.storefront.wish_lists.wish_list_title')
10
12
 
11
13
  .wish-lists.view
12
14
 
@@ -55,6 +57,8 @@
55
57
 
56
58
  %ul.product-list
57
59
  - @wish_list.items.each do |item|
60
+ = render_schema_org(product_schema(item.product))
61
+
58
62
  %li.product-list__item
59
63
  .product-list__item-cell
60
64
  .product-list__summary
@@ -114,12 +118,13 @@
114
118
  = form_tag cart_items_path, data: { dialog_form: { dialogOptions: { closeAll: true, initModules: true } }, analytics: add_to_cart_analytics_data(item.product).to_json } do
115
119
  = hidden_field_tag :sku, item.sku, id: nil
116
120
  = hidden_field_tag :quantity, item.quantity, id: nil
121
+ = hidden_field_tag :via, @wish_list.to_gid_param, id: nil
117
122
  - item.customizations.each do |param, value|
118
123
  = hidden_field_tag param, value, id: nil
119
124
  %p= button_tag t('workarea.storefront.products.add_to_cart'), value: 'add_to_cart', class: 'button'
120
125
  - else
121
126
  %p.unavailable= t('workarea.storefront.products.unavailable')
122
127
  .grid__cell
123
- = form_tag remove_from_users_wish_list_path, method: 'delete' do
128
+ = form_tag remove_from_users_wish_list_path, method: 'delete', data: { analytics: remove_from_wish_list_analytics_data(item.product).to_json } do
124
129
  = hidden_field_tag :sku, item.sku, id: nil
125
130
  %p= button_tag t('workarea.storefront.carts.remove'), value: 'remove_item', class: 'text-button'
@@ -1,12 +1,14 @@
1
1
  - @title = t('workarea.storefront.wish_lists.show_title', name: @wish_list.name)
2
2
 
3
+ = render_schema_org(breadcrumb_list_schema([[t('workarea.storefront.layouts.home'), root_path], [t('workarea.storefront.wish_lists.find_title'), wish_lists_path], [@wish_list.name, wish_list_path(@wish_list)]]))
4
+
3
5
  - content_for :breadcrumbs do
4
6
  %p.breadcrumbs__node-group
5
- %span.breadcrumbs__node{ itemprop: 'breadcrumb' }
7
+ %span.breadcrumbs__node
6
8
  = link_to t('workarea.storefront.layouts.home'), root_path, rel: 'home'
7
- %span.breadcrumbs__node{ itemprop: 'breadcrumb' }
9
+ %span.breadcrumbs__node
8
10
  = link_to t('workarea.storefront.wish_lists.find_title'), wish_lists_path
9
- %span.breadcrumbs__node{ itemprop: 'breadcrumb' }= t('workarea.storefront.wish_lists.show_title', name: @wish_list.name)
11
+ %span.breadcrumbs__node= t('workarea.storefront.wish_lists.show_title', name: @wish_list.name)
10
12
 
11
13
  .wish-lists.view
12
14
 
@@ -30,6 +32,8 @@
30
32
 
31
33
  %ul.product-list
32
34
  - @wish_list.items.each do |item|
35
+ = render_schema_org(product_schema(item.product))
36
+
33
37
  %li.product-list__item
34
38
  .product-list__item-cell
35
39
  .product-list__summary
@@ -47,6 +51,7 @@
47
51
  = form_tag cart_items_path, class: 'product-list__item-cell', data: { dialog_form: { dialogOptions: { closeAll: true, initModules: true } }, analytics: add_to_cart_analytics_data(item.product).to_json } do
48
52
  = hidden_field_tag :product_id, item.product_id, id: nil
49
53
  = hidden_field_tag :sku, item.sku, id: nil
54
+ = hidden_field_tag :via, @wish_list.to_gid_param, id: nil
50
55
  - item.customizations.each do |param, value|
51
56
  = hidden_field_tag param, value, id: nil
52
57
  %table.table
@@ -0,0 +1,21 @@
1
+ module Workarea
2
+ decorate SaveOrderMetrics, with: :wish_lists do
3
+ class_methods do
4
+ def save_catalog(metrics)
5
+ super
6
+
7
+ save_wish_lists(metrics)
8
+ end
9
+
10
+ def save_wish_lists(metrics)
11
+ metrics.products_from_wish_lists.each do |product_id, data|
12
+ Metrics::ProductByDay.inc(
13
+ key: { product_id: product_id },
14
+ at: metrics.placed_at,
15
+ **data
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -12,7 +12,8 @@ Workarea.append_javascripts(
12
12
  Workarea.append_javascripts(
13
13
  'storefront.modules',
14
14
  'workarea/storefront/wish_lists/modules/wish_list_button',
15
- 'workarea/storefront/wish_lists/modules/wish_list_public_quantity_fields'
15
+ 'workarea/storefront/wish_lists/modules/wish_list_public_quantity_fields',
16
+ 'workarea/storefront/wish_lists/modules/wish_list_analytics'
16
17
  )
17
18
 
18
19
  Workarea.append_partials(
@@ -40,7 +41,17 @@ Workarea.append_partials(
40
41
  'workarea/storefront/wish_lists/wish_lists_link'
41
42
  )
42
43
 
44
+ Workarea.append_partials(
45
+ 'storefront.robots_txt',
46
+ 'workarea/storefront/pages/wish_lists_robots'
47
+ )
48
+
43
49
  Workarea.append_partials(
44
50
  'admin.user_cards',
45
51
  'workarea/admin/users/wish_list_card'
46
52
  )
53
+
54
+ Workarea.append_partials(
55
+ 'admin.reports_dashboard',
56
+ 'workarea/admin/dashboards/wish_list_products_card'
57
+ )
@@ -19,6 +19,26 @@ en:
19
19
  state: State
20
20
  title: Wish list for %{user}
21
21
  title_html: Wish list for %{user}
22
+ fields:
23
+ adds: Adds
24
+ deletes: Deletes
25
+ insights:
26
+ most_wish_listed_products:
27
+ title: Most Wish Listed Products
28
+ info: Your products most frequently added to a wish list.
29
+ times:
30
+ one: '%{count} time'
31
+ other: '%{count} times'
32
+ reports:
33
+ reference:
34
+ adds:
35
+ name: Adds
36
+ description: The total number of items added to a list.
37
+ deletes:
38
+ name: Deletes
39
+ description: The total number of items deleted from a list
40
+ wish_list_products:
41
+ title: Wish List Products
22
42
  storefront:
23
43
  flash_messages:
24
44
  item_moved_to_wish_list: This item has been moved to your wish list
@@ -3,6 +3,10 @@ Workarea::Admin::Engine.routes.draw do
3
3
  resources :users, only: [] do
4
4
  get :wish_list
5
5
  end
6
+
7
+ resource :report, only: [] do
8
+ get :wish_list_products
9
+ end
6
10
  end
7
11
  end
8
12
 
@@ -19,5 +23,8 @@ Workarea::Storefront::Engine.routes.draw do
19
23
  delete 'remove_item', as: :remove_from
20
24
  end
21
25
  end
26
+
27
+ post 'analytics/wish_list_add/:product_id', to: 'analytics#wish_list_add', as: :analytics_wish_list_add
28
+ post 'analytics/wish_list_remove/:product_id', to: 'analytics#wish_list_remove', as: :analytics_wish_list_remove
22
29
  end
23
30
  end
@@ -10,6 +10,12 @@ module Workarea
10
10
  .action_dispatch
11
11
  .rescue_responses['Workarea::WishList::InvalidToken'] = :not_found
12
12
  end
13
+
14
+ config.to_prepare do
15
+ Workarea::Storefront::ApplicationController.helper(
16
+ Workarea::Storefront::WishListAnalyticsHelper
17
+ )
18
+ end
13
19
  end
14
20
  end
15
21
  end
@@ -1,5 +1,5 @@
1
1
  module Workarea
2
2
  module WishLists
3
- VERSION = '3.0.5'.freeze
3
+ VERSION = '3.1.0'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,9 @@
1
+ {
2
+ "devDependencies": {
3
+ "eslint": "~5.16.0",
4
+ "eslint-config-recommended": "~4.0.0",
5
+ "stylelint": "~10.1.0",
6
+ "stylelint-config-recommended-scss": "~3.3.0",
7
+ "stylelint-scss": "~3.9.2"
8
+ }
9
+ }
@@ -1,3 +1,5 @@
1
1
  # Be sure to restart your server when you modify this file.
2
2
 
3
- Rails.application.config.session_store :cookie_store, key: '_dummy_session'
3
+ Rails.application.config.session_store :cookie_store,
4
+ key: '_dummy_session',
5
+ expire_after: 30.minutes
@@ -194,7 +194,7 @@ module Workarea
194
194
 
195
195
  assert_redirected_to(storefront.login_path)
196
196
  refute_empty(order.reload.items)
197
- assert_equal(item.id.to_s, session[:wish_list_item_id])
197
+ assert(cookies[:wish_list_item_id].present?)
198
198
 
199
199
  login
200
200
 
@@ -203,7 +203,7 @@ module Workarea
203
203
  follow_redirect!
204
204
 
205
205
  assert_response(:success)
206
- refute(session.key?(:wish_list_item_id))
206
+ assert(cookies[:wish_list_item_id].blank?)
207
207
  assert_empty(order.reload.items)
208
208
  end
209
209
  end