solidus_admin_insights 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +54 -0
  3. data/Gemfile +2 -0
  4. data/LICENSE +26 -0
  5. data/README.md +71 -0
  6. data/Rakefile +21 -0
  7. data/app/assets/javascripts/spree/backend/jquery.tablesorter.min.js +4 -0
  8. data/app/assets/javascripts/spree/backend/solidus_admin_insights.js +2 -0
  9. data/app/assets/javascripts/spree/backend/solidus_admin_insights/paginator.js +128 -0
  10. data/app/assets/javascripts/spree/backend/solidus_admin_insights/report_loader.js +265 -0
  11. data/app/assets/javascripts/spree/backend/solidus_admin_insights/searcher.js +72 -0
  12. data/app/assets/javascripts/spree/backend/solidus_admin_insights/table_sorter.js +47 -0
  13. data/app/assets/javascripts/spree/backend/tmpl.js +87 -0
  14. data/app/assets/javascripts/spree/frontend/solidus_admin_insights.js +2 -0
  15. data/app/assets/stylesheets/spree/backend/override_pdf.css +71 -0
  16. data/app/assets/stylesheets/spree/backend/solidus_admin_insights.css +132 -0
  17. data/app/assets/stylesheets/spree/frontend/solidus_admin_insights.css +4 -0
  18. data/app/controllers/spree/admin/insights_controller.rb +103 -0
  19. data/app/helpers/spree/admin/base_helper_decorator.rb +17 -0
  20. data/app/models/spree/app_configuration_decorator.rb +3 -0
  21. data/app/models/spree/product_decorator.rb +3 -0
  22. data/app/models/spree/promotion_action_decorator.rb +3 -0
  23. data/app/models/spree/return_authorization_decorator.rb +4 -0
  24. data/app/permissions/spree/permission_sets/insight_display.rb +9 -0
  25. data/app/reports/spree/best_selling_products_report.rb +42 -0
  26. data/app/reports/spree/cart_additions_report.rb +36 -0
  27. data/app/reports/spree/cart_removals_report.rb +36 -0
  28. data/app/reports/spree/cart_updations_report.rb +40 -0
  29. data/app/reports/spree/payment_method_transactions_conversion_rate_report.rb +74 -0
  30. data/app/reports/spree/payment_method_transactions_conversion_rate_report/payment_method_state_distribution_chart.rb +39 -0
  31. data/app/reports/spree/payment_method_transactions_report.rb +60 -0
  32. data/app/reports/spree/payment_method_transactions_report/payment_method_revenue_distribution_chart.rb +36 -0
  33. data/app/reports/spree/product_views_report.rb +46 -0
  34. data/app/reports/spree/product_views_to_cart_additions_report.rb +53 -0
  35. data/app/reports/spree/product_views_to_purchases_report.rb +54 -0
  36. data/app/reports/spree/promotional_cost_report.rb +84 -0
  37. data/app/reports/spree/promotional_cost_report/promotional_cost_chart.rb +37 -0
  38. data/app/reports/spree/promotional_cost_report/usage_count_chart.rb +41 -0
  39. data/app/reports/spree/report.rb +131 -0
  40. data/app/reports/spree/report/chart.rb +11 -0
  41. data/app/reports/spree/report/configuration.rb +40 -0
  42. data/app/reports/spree/report/date_slicer.rb +61 -0
  43. data/app/reports/spree/report/observation.rb +49 -0
  44. data/app/reports/spree/report/query_fragments.rb +45 -0
  45. data/app/reports/spree/report/query_time_scale.rb +19 -0
  46. data/app/reports/spree/report/result.rb +100 -0
  47. data/app/reports/spree/report/timed_observation.rb +47 -0
  48. data/app/reports/spree/report/timed_result.rb +48 -0
  49. data/app/reports/spree/returned_products_report.rb +37 -0
  50. data/app/reports/spree/sales_performance_report.rb +107 -0
  51. data/app/reports/spree/sales_performance_report/profit_loss_chart.rb +37 -0
  52. data/app/reports/spree/sales_performance_report/profit_loss_percent_chart.rb +36 -0
  53. data/app/reports/spree/sales_performance_report/sale_cost_price_chart.rb +48 -0
  54. data/app/reports/spree/sales_tax_report.rb +64 -0
  55. data/app/reports/spree/sales_tax_report/monthly_sales_tax_comparison_chart.rb +39 -0
  56. data/app/reports/spree/shipping_cost_report.rb +89 -0
  57. data/app/reports/spree/shipping_cost_report/shipping_cost_distribution_chart.rb +38 -0
  58. data/app/reports/spree/trending_search_report.rb +50 -0
  59. data/app/reports/spree/trending_search_report/frequency_distribution_pie_chart.rb +41 -0
  60. data/app/reports/spree/unique_purchases_report.rb +39 -0
  61. data/app/reports/spree/user_pool_report.rb +66 -0
  62. data/app/reports/spree/user_pool_report/distribution_column_chart.rb +65 -0
  63. data/app/reports/spree/users_not_converted_report.rb +48 -0
  64. data/app/reports/spree/users_who_recently_purchased_report.rb +69 -0
  65. data/app/services/spree/report_generation_service.rb +27 -0
  66. data/app/views/spree/admin/insights/_chart.html.erb +4 -0
  67. data/app/views/spree/admin/insights/download.pdf.erb +27 -0
  68. data/app/views/spree/admin/insights/index.html.erb +82 -0
  69. data/app/views/spree/admin/insights/search/_product_views_search.html.erb +13 -0
  70. data/app/views/spree/admin/insights/search/_search_form.html.erb +39 -0
  71. data/app/views/spree/admin/insights/search/_trending_searches_search.html.erb +13 -0
  72. data/app/views/spree/admin/insights/search/_users_not_converted_search.html.erb +13 -0
  73. data/app/views/spree/admin/insights/search/_users_who_have_not_purchased_recently_search.html.erb +13 -0
  74. data/app/views/spree/admin/insights/search/_users_who_recently_purchased_search.html.erb +13 -0
  75. data/app/views/spree/admin/shared/_insights_side_menu.html.erb +5 -0
  76. data/app/views/spree/admin/shared/sub_menu/_insight.html.erb +7 -0
  77. data/app/views/spree/admin/templates/insights/_paginator.template +11 -0
  78. data/app/views/spree/admin/templates/insights/_search.template +76 -0
  79. data/app/views/spree/admin/templates/insights/_show.template +49 -0
  80. data/app/views/spree/layouts/pdf.html.erb +9 -0
  81. data/bin/rails +7 -0
  82. data/config/initializers/add_to_sidebar.rb +14 -0
  83. data/config/initializers/assets.rb +1 -0
  84. data/config/initializers/mime_types.rb +2 -0
  85. data/config/initializers/wicked_pdf.rb +21 -0
  86. data/config/locales/en.yml +167 -0
  87. data/config/routes.rb +6 -0
  88. data/lib/generators/solidus_admin_insights/install/install_generator.rb +36 -0
  89. data/lib/generators/solidus_admin_insights/install/solidus_admin_insights.rb +22 -0
  90. data/lib/solidus_admin_insights.rb +14 -0
  91. data/lib/solidus_admin_insights/engine.rb +27 -0
  92. data/lib/solidus_admin_insights/factories.rb +6 -0
  93. data/solidus_admin_insights.gemspec +42 -0
  94. data/spec/spec_helper.rb +93 -0
  95. metadata +419 -0
@@ -0,0 +1,17 @@
1
+ Spree::BaseHelper.class_eval do
2
+ def selected?(path)
3
+ path == 'spree/admin/insights'
4
+ end
5
+
6
+ def form_action(insight, insight_type)
7
+ insight ? admin_insight_path(id: @report_name, type: insight_type) : 'javascript:void(0)'
8
+ end
9
+
10
+ def page_selector_options
11
+ [5, 10, 15, 30, 45, 60]
12
+ end
13
+
14
+ def pdf_logo(image_path = Spree::Config[:logo])
15
+ wicked_pdf_image_tag image_path, class: 'logo'
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ Spree::AppConfiguration.class_eval do
2
+ preference :records_per_page, :integer, default: 5
3
+ end
@@ -0,0 +1,3 @@
1
+ Spree::Product.class_eval do
2
+ has_many :page_view_events, -> { viewed.product }, class_name: 'Spree::PageEvent', foreign_key: :target_id
3
+ end
@@ -0,0 +1,3 @@
1
+ Spree::PromotionAction.class_eval do
2
+ has_one :adjustment, -> { promotion }, class_name: 'Spree::Adjustment', foreign_key: :source_id
3
+ end
@@ -0,0 +1,4 @@
1
+ Spree::ReturnAuthorization.class_eval do
2
+ has_many :variants, through: :inventory_units
3
+ has_many :products, through: :variants
4
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module PermissionSets
3
+ class InsightDisplay < PermissionSets::Base
4
+ def activate!
5
+ can [:admin, :display, :download], :insights
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ module Spree
2
+ class BestSellingProductsReport < Spree::Report
3
+ DEFAULT_SORTABLE_ATTRIBUTE = :sold_count
4
+ HEADERS = { sku: :string, product_name: :string, sold_count: :integer }
5
+ SEARCH_ATTRIBUTES = { start_date: :orders_completed_from, end_date: :orders_completed_to }
6
+ SORTABLE_ATTRIBUTES = [:product_name, :sku, :sold_count]
7
+
8
+ deeplink product_name: { template: %Q{<a href="/admin/products/{%# o.product_slug %}" target="_blank">{%# o.product_name %}</a>} }
9
+
10
+ class Result < Spree::Report::Result
11
+ class Observation < Spree::Report::Observation
12
+ observation_fields [:product_name, :product_slug, :sku, :sold_count]
13
+
14
+ def sku
15
+ @sku.presence || @product_name
16
+ end
17
+ end
18
+ end
19
+
20
+ def report_query
21
+ Spree::LineItem
22
+ .joins(:order)
23
+ .joins(:variant)
24
+ .joins(:product)
25
+ .where(Spree::Product.arel_table[:name].matches(search_name))
26
+ .where(spree_orders: { state: 'complete' })
27
+ .where(spree_orders: { completed_at: reporting_period })
28
+ .group(:variant_id, :product_name, :product_slug, 'spree_variants.sku')
29
+ .select(
30
+ 'spree_products.name as product_name',
31
+ 'spree_products.slug as product_slug',
32
+ 'spree_variants.sku as sku',
33
+ 'sum(quantity) as sold_count'
34
+ )
35
+ end
36
+
37
+ private def search_name
38
+ search[:name].present? ? "%#{ search[:name] }%" : '%'
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ module Spree
2
+ class CartAdditionsReport < Spree::Report
3
+ DEFAULT_SORTABLE_ATTRIBUTE = :product_name
4
+ HEADERS = { sku: :string, product_name: :string, additions: :integer, quantity_change: :integer }
5
+ SEARCH_ATTRIBUTES = { start_date: :product_added_from, end_date: :product_added_to }
6
+ SORTABLE_ATTRIBUTES = [:product_name, :sku, :additions, :quantity_change]
7
+
8
+ deeplink product_name: { template: %Q{<a href="/admin/products/{%# o.product_slug %}" target="_blank">{%# o.product_name %}</a>} }
9
+
10
+ class Result < Spree::Report::Result
11
+ class Observation < Spree::Report::Observation
12
+ observation_fields [:product_name, :product_slug, :additions, :quantity_change, :sku]
13
+
14
+ def sku
15
+ @sku.presence || @product_name
16
+ end
17
+ end
18
+ end
19
+
20
+ def report_query
21
+ Spree::CartEvent
22
+ .added
23
+ .joins(:variant)
24
+ .joins(:product)
25
+ .where(created_at: reporting_period)
26
+ .group('product_name', 'product_slug', 'spree_variants.sku')
27
+ .select(
28
+ 'spree_products.name as product_name',
29
+ 'spree_products.slug as product_slug',
30
+ 'spree_variants.sku as sku',
31
+ 'count(spree_products.name) as additions',
32
+ 'sum(spree_cart_events.quantity) as quantity_change'
33
+ )
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module Spree
2
+ class CartRemovalsReport < Spree::Report
3
+ DEFAULT_SORTABLE_ATTRIBUTE = :product_name
4
+ HEADERS = { sku: :string, product_name: :string, removals: :integer, quantity_change: :integer }
5
+ SEARCH_ATTRIBUTES = { start_date: :product_removed_from, end_date: :product_removed_to }
6
+ SORTABLE_ATTRIBUTES = [:product_name, :sku, :removals, :quantity_change]
7
+
8
+ deeplink product_name: { template: %Q{<a href="/admin/products/{%# o.product_slug %}" target="_blank">{%# o.product_name %}</a>} }
9
+
10
+ class Result < Spree::Report::Result
11
+ class Observation < Spree::Report::Observation
12
+ observation_fields [:product_name, :product_slug, :removals, :quantity_change, :sku]
13
+
14
+ def sku
15
+ @sku.presence || @product_name
16
+ end
17
+ end
18
+ end
19
+
20
+ def report_query
21
+ Spree::CartEvent
22
+ .removed
23
+ .joins(:variant)
24
+ .joins(:product)
25
+ .where(created_at: reporting_period)
26
+ .group('product_name', 'product_slug', 'spree_variants.sku')
27
+ .select(
28
+ 'spree_products.name as product_name',
29
+ 'spree_products.slug as product_slug',
30
+ 'spree_variants.sku as sku',
31
+ 'count(spree_products.name) as removals',
32
+ 'sum(spree_cart_events.quantity) as quantity_change'
33
+ )
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ module Spree
2
+ class CartUpdationsReport < Spree::Report
3
+ DEFAULT_SORTABLE_ATTRIBUTE = :product_name
4
+ HEADERS = { sku: :string, product_name: :string, updations: :integer, quantity_increase: :integer, quantity_decrease: :integer }
5
+ SEARCH_ATTRIBUTES = { start_date: :product_updated_from, end_date: :product_updated_to }
6
+ SORTABLE_ATTRIBUTES = [:product_name, :sku, :updations, :quantity_increase, :quantity_decrease]
7
+
8
+ deeplink product_name: { template: %Q{<a href="/admin/products/{%# o.product_slug %}" target="_blank">{%# o.product_name %}</a>} }
9
+
10
+ class Result < Spree::Report::Result
11
+ class Observation < Spree::Report::Observation
12
+ observation_fields [:product_name, :product_slug, :updations, :quantity_increase, :sku, :quantity_decrease]
13
+
14
+ def sku
15
+ @sku.presence || @product_name
16
+ end
17
+ end
18
+ end
19
+
20
+ def report_query
21
+ quantity_increase_sql = "CASE WHEN quantity > 0 then spree_cart_events.quantity ELSE 0 END"
22
+ quantity_decrease_sql = "CASE WHEN quantity < 0 then spree_cart_events.quantity ELSE 0 END"
23
+
24
+ Spree::CartEvent
25
+ .updated
26
+ .joins(:variant)
27
+ .joins(:product)
28
+ .where(created_at: reporting_period)
29
+ .group('product_name', 'product_slug', 'spree_variants.sku')
30
+ .select(
31
+ 'spree_products.name as product_name',
32
+ 'spree_products.slug as product_slug',
33
+ 'spree_variants.sku as sku',
34
+ 'count(spree_products.name) as updations',
35
+ "SUM(#{ quantity_increase_sql }) as quantity_increase",
36
+ "SUM(#{ quantity_decrease_sql }) as quantity_decrease"
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,74 @@
1
+ module Spree
2
+ class PaymentMethodTransactionsConversionRateReport < Spree::Report
3
+ DEFAULT_SORTABLE_ATTRIBUTE = :payment_method_name
4
+ HEADERS = { payment_method_name: :string, payment_state: :string, months_name: :string, count: :integer }
5
+ SEARCH_ATTRIBUTES = { start_date: :payments_created_from, end_date: :payments_created_to }
6
+ SORTABLE_ATTRIBUTES = [:payment_method_name, :successful_payments_count, :failed_payments_count, :pending_payments_count, :invalid_payments_count]
7
+
8
+ class Result < Spree::Report::TimedResult
9
+ charts PaymentMethodStateDistributionChart
10
+
11
+ def build_empty_observations
12
+ super
13
+ @_payment_methods = @results.collect { |result| result['payment_method_name'] }.uniq
14
+ @observations = @_payment_methods.collect do |payment_method_name|
15
+ payment_states = @results
16
+ .select { |result| result['payment_method_name'] == payment_method_name }
17
+ .collect { |result| result['payment_state'] }
18
+ .uniq
19
+
20
+ payment_states.collect do |state|
21
+ @observations.collect do |observation|
22
+ _d_observation = observation.dup
23
+ _d_observation.payment_method_name = payment_method_name
24
+ _d_observation.payment_state = state
25
+ _d_observation.count = 0
26
+ _d_observation
27
+ end
28
+ end
29
+ end.flatten
30
+ end
31
+
32
+ class Observation < Spree::Report::TimedObservation
33
+ observation_fields [:payment_method_name, :payment_state, :count]
34
+
35
+ def payment_state
36
+ if @payment_state == 'pending'
37
+ @payment_state
38
+ else
39
+ "capturing #{ @payment_state }"
40
+ end
41
+ end
42
+
43
+ def describes?(result, time_scale)
44
+ (result['payment_method_name'] == payment_method_name && result['payment_state'] == @payment_state) && super
45
+ end
46
+ end
47
+ end
48
+
49
+ def report_query
50
+ Spree::Report::QueryFragments
51
+ .from_subquery(payment_methods)
52
+ .group(*time_scale_columns_to_s, 'payment_method_name', 'payment_state')
53
+ .order(*time_scale_columns)
54
+ .project(
55
+ *time_scale_columns,
56
+ 'payment_method_name',
57
+ 'payment_state',
58
+ 'COUNT(payment_method_id) as count'
59
+ )
60
+ end
61
+
62
+ private def payment_methods
63
+ Spree::PaymentMethod
64
+ .joins(:payments)
65
+ .where(spree_payments: { created_at: reporting_period })
66
+ .select(
67
+ 'spree_payment_methods.id as payment_method_id',
68
+ 'name as payment_method_name',
69
+ 'state as payment_state',
70
+ *time_scale_selects('spree_payments')
71
+ )
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,39 @@
1
+ class Spree::PaymentMethodTransactionsConversionRateReport::PaymentMethodStateDistributionChart
2
+ attr_accessor :chart_data
3
+
4
+ def initialize(result)
5
+ @time_dimension = result.time_dimension
6
+ @grouped_by_payment_method = result.observations.group_by(&:payment_method_name)
7
+ @time_series = []
8
+ @time_series = @grouped_by_payment_method.values.first.collect { |observation| observation.send(@time_dimension) } if @grouped_by_payment_method.first.present?
9
+ end
10
+
11
+ def to_h
12
+ @grouped_by_payment_method.collect do |method_name, observations|
13
+ {
14
+ id: 'payment-state-' + method_name,
15
+ json: {
16
+ chart: { type: 'column' },
17
+ title: {
18
+ useHTML: true,
19
+ text: %Q(<span class='chart-title'>#{ method_name } Conversion Status</span>
20
+ <span class='fa fa-question-circle' data-toggle='tooltip' title=' Tracks the status of Payments made from different payment methods such as CC, Check etc.'></span>)
21
+ },
22
+
23
+ xAxis: { categories: @time_series },
24
+ yAxis: {
25
+ title: { text: 'Count' }
26
+ },
27
+ tooltip: { valuePrefix: '#' },
28
+ legend: {
29
+ layout: 'vertical',
30
+ align: 'right',
31
+ verticalAlign: 'middle',
32
+ borderWidth: 0
33
+ },
34
+ series: observations.group_by(&:payment_state).map { |key, value| { name: key, data: value.map(&:count) } }
35
+ }
36
+ }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ module Spree
2
+ class PaymentMethodTransactionsReport < Spree::Report
3
+ DEFAULT_SORTABLE_ATTRIBUTE = :payment_method_name
4
+ HEADERS = { payment_method_name: :string, payment_amount: :integer }
5
+ SEARCH_ATTRIBUTES = { start_date: :payments_created_from, end_date: :payments_created_till }
6
+ SORTABLE_ATTRIBUTES = []
7
+
8
+ class Result < Spree::Report::TimedResult
9
+ charts PaymentMethodRevenueDistributionChart
10
+
11
+ def build_empty_observations
12
+ super
13
+ @_payment_methods = @results.collect { |result| result['payment_method_name'] }.uniq
14
+ @observations = @_payment_methods.collect do |payment_method_name|
15
+ @observations.collect do |observation|
16
+ _d_observation = observation.dup
17
+ _d_observation.payment_amount = 0
18
+ _d_observation.payment_method_name = payment_method_name
19
+ _d_observation
20
+ end
21
+ end.flatten
22
+ end
23
+
24
+ class Observation < Spree::Report::TimedObservation
25
+ observation_fields [:payment_method_name, :payment_amount]
26
+
27
+ def describes?(result, time_scale)
28
+ (result['payment_method_name'] == payment_method_name) && super
29
+ end
30
+
31
+ def payment_amount
32
+ @payment_amount.to_f
33
+ end
34
+ end
35
+ end
36
+
37
+ def report_query
38
+ Spree::Report::QueryFragments
39
+ .from_subquery(payments)
40
+ .group(*time_scale_columns_to_s, 'payment_method_name')
41
+ .order(*time_scale_columns)
42
+ .project(
43
+ *time_scale_columns,
44
+ 'payment_method_name',
45
+ 'SUM(payment_amount) as payment_amount'
46
+ )
47
+ end
48
+
49
+ private def payments
50
+ Spree::PaymentMethod
51
+ .joins(:payments)
52
+ .where(spree_payments: { created_at: reporting_period })
53
+ .select(
54
+ *time_scale_selects('spree_payments'),
55
+ 'spree_payment_methods.name as payment_method_name',
56
+ 'spree_payments.amount as payment_amount',
57
+ )
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,36 @@
1
+ class Spree::PaymentMethodTransactionsReport::PaymentMethodRevenueDistributionChart
2
+ def initialize(result)
3
+ @time_dimension = result.time_dimension
4
+ @grouped_by_payment_method = result.observations.group_by(&:payment_method_name)
5
+ @time_series = []
6
+ if @grouped_by_payment_method.values.first.present?
7
+ @time_series = @grouped_by_payment_method.values.first.collect { |observation| observation.send(@time_dimension) }
8
+ end
9
+ end
10
+
11
+ def to_h
12
+ {
13
+ id: 'payment-methods',
14
+ json: {
15
+ chart: { type: 'column' },
16
+ title: {
17
+ useHTML: true,
18
+ text: "<span class='chart-title'>Payment Methods</span><span class='fa fa-question-circle' data-toggle='tooltip' title=' Compare the revenue generated by different Payment methods such as CC, Check etc.'></span>"
19
+ },
20
+ xAxis: { categories: @time_series },
21
+ yAxis: {
22
+ title: { text: 'value($)' }
23
+ },
24
+ tooltip: { valuePrefix: '$' },
25
+ legend: {
26
+ layout: 'vertical',
27
+ align: 'right',
28
+ verticalAlign: 'middle',
29
+ borderWidth: 0
30
+ },
31
+ series: @grouped_by_payment_method.collect { |key, value| { name: key, data: value.map(&:payment_amount) }
32
+ }
33
+ }
34
+ }
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ module Spree
2
+ class ProductViewsReport < Spree::Report
3
+ DEFAULT_SORTABLE_ATTRIBUTE = :product_name
4
+ HEADERS = { product_name: :string, views: :integer, users: :integer, guest_sessions: :integer }
5
+ SEARCH_ATTRIBUTES = { start_date: :product_view_from, end_date: :product_view_till, name: :name}
6
+ SORTABLE_ATTRIBUTES = [:product_name, :views, :users, :guest_sessions]
7
+
8
+ deeplink product_name: { template: %Q{<a href="/admin/products/{%# o.product_slug %}" target="_blank">{%# o.product_name %}</a>} }
9
+
10
+ class Result < Spree::Report::Result
11
+ class Observation < Spree::Report::Observation
12
+ observation_fields [:product_name, :product_slug, :views, :users, :guest_sessions]
13
+ end
14
+ end
15
+
16
+ def report_query
17
+ viewed_events =
18
+ Spree::Product
19
+ .where(Spree::Product.arel_table[:name].matches(search_name))
20
+ .joins(:page_view_events)
21
+ .where(spree_page_events: { created_at: reporting_period })
22
+ .group('product_name', 'product_slug', 'spree_page_events.actor_id', 'spree_page_events.session_id')
23
+ .select(
24
+ 'spree_products.name as product_name',
25
+ 'spree_products.slug as product_slug',
26
+ 'COUNT(*) as total_views_per_session',
27
+ 'spree_page_events.session_id as session_id',
28
+ 'spree_page_events.actor_id as actor_id'
29
+ )
30
+ Spree::Report::QueryFragments
31
+ .from_subquery(viewed_events)
32
+ .group('product_name', 'product_slug')
33
+ .project(
34
+ 'product_name',
35
+ 'product_slug',
36
+ 'SUM(total_views_per_session) as views',
37
+ 'COUNT(DISTINCT actor_id) as users',
38
+ '(COUNT(DISTINCT session_id) - COUNT(actor_id)) as guest_sessions'
39
+ )
40
+ end
41
+
42
+ private def search_name
43
+ search[:name].present? ? "%#{ search[:name] }%" : '%'
44
+ end
45
+ end
46
+ end