ahoy_captain 0.77 → 0.82

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascript/ahoy_captain/application.js +4 -4
  3. data/app/assets/javascript/ahoy_captain/controllers/active_links_controller.js +15 -0
  4. data/app/assets/javascript/ahoy_captain/controllers/application.js +5 -5
  5. data/app/assets/javascript/ahoy_captain/controllers/application_controller.js +9 -10
  6. data/app/assets/javascript/ahoy_captain/controllers/details_modal_controller.js +5 -5
  7. data/app/assets/javascript/ahoy_captain/controllers/dropdown_label_controller.js +2 -2
  8. data/app/assets/javascript/ahoy_captain/controllers/filter_form_controller.js +13 -0
  9. data/app/assets/javascript/ahoy_captain/controllers/filter_tag_controller.js +11 -8
  10. data/app/assets/javascript/ahoy_captain/controllers/funnel_chart_controller.js +77 -133
  11. data/app/assets/javascript/ahoy_captain/controllers/index.js +4 -3
  12. data/app/assets/javascript/ahoy_captain/controllers/interval_controller.js +10 -0
  13. data/app/assets/javascript/ahoy_captain/controllers/line_chart_controller.js +37 -0
  14. data/app/assets/javascript/ahoy_captain/controllers/predicate_select_controller.js +10 -0
  15. data/app/assets/javascript/ahoy_captain/controllers/realtime_controller.js +9 -8
  16. data/app/assets/javascript/ahoy_captain/controllers/search_select_controller.js +65 -0
  17. data/app/components/ahoy_captain/filter/modal_component.html.erb +6 -5
  18. data/app/components/ahoy_captain/filter/select_component.html.erb +13 -11
  19. data/app/components/ahoy_captain/filter/select_component.rb +21 -4
  20. data/app/components/ahoy_captain/table_component.html.erb +2 -35
  21. data/app/components/ahoy_captain/table_component.rb +13 -5
  22. data/app/components/ahoy_captain/tables/headers/devices_header_component.html.erb +3 -0
  23. data/app/components/ahoy_captain/tables/headers/devices_header_component.rb +9 -0
  24. data/app/components/ahoy_captain/tables/headers/goals_header_component.html.erb +6 -0
  25. data/app/components/ahoy_captain/tables/headers/goals_header_component.rb +9 -0
  26. data/app/components/ahoy_captain/tables/headers/header_component.html.erb +5 -0
  27. data/app/components/ahoy_captain/tables/headers/header_component.rb +12 -0
  28. data/app/components/ahoy_captain/tables/rows/devices_row_component.html.erb +5 -0
  29. data/app/components/ahoy_captain/tables/rows/devices_row_component.rb +12 -0
  30. data/app/components/ahoy_captain/tables/rows/goals_row_component.html.erb +11 -0
  31. data/app/components/ahoy_captain/tables/rows/goals_row_component.rb +12 -0
  32. data/app/components/ahoy_captain/tables/rows/row_component.html.erb +6 -0
  33. data/app/components/ahoy_captain/tables/rows/row_component.rb +41 -0
  34. data/app/controllers/ahoy_captain/application_controller.rb +17 -15
  35. data/app/controllers/ahoy_captain/campaigns_controller.rb +2 -10
  36. data/app/controllers/ahoy_captain/cities_controller.rb +2 -6
  37. data/app/controllers/ahoy_captain/countries_controller.rb +2 -6
  38. data/app/controllers/ahoy_captain/devices_controller.rb +3 -6
  39. data/app/controllers/ahoy_captain/entry_pages_controller.rb +2 -4
  40. data/app/controllers/ahoy_captain/exit_pages_controller.rb +3 -4
  41. data/app/controllers/ahoy_captain/exports_controller.rb +15 -0
  42. data/app/controllers/ahoy_captain/filters/pages/entry_pages_controller.rb +2 -2
  43. data/app/controllers/ahoy_captain/filters/pages/exit_pages_controller.rb +1 -2
  44. data/app/controllers/ahoy_captain/filters/properties/names_controller.rb +11 -0
  45. data/app/controllers/ahoy_captain/filters/properties/values_controller.rb +15 -0
  46. data/app/controllers/ahoy_captain/realtimes_controller.rb +1 -1
  47. data/app/controllers/ahoy_captain/regions_controller.rb +3 -7
  48. data/app/controllers/ahoy_captain/sources_controller.rb +2 -5
  49. data/app/controllers/ahoy_captain/stats/base_controller.rb +61 -0
  50. data/app/controllers/ahoy_captain/stats/bounce_rates_controller.rb +4 -2
  51. data/app/controllers/ahoy_captain/stats/total_pageviews_controller.rb +2 -1
  52. data/app/controllers/ahoy_captain/stats/total_visits_controller.rb +2 -1
  53. data/app/controllers/ahoy_captain/stats/unique_visitors_controller.rb +2 -1
  54. data/app/controllers/ahoy_captain/stats/views_per_visits_controller.rb +10 -6
  55. data/app/controllers/ahoy_captain/stats/visit_durations_controller.rb +2 -1
  56. data/app/controllers/ahoy_captain/top_pages_controller.rb +2 -8
  57. data/app/decorators/ahoy_captain/application_decorator.rb +27 -3
  58. data/app/decorators/ahoy_captain/campaign_decorator.rb +8 -0
  59. data/app/decorators/ahoy_captain/city_decorator.rb +12 -0
  60. data/app/decorators/ahoy_captain/country_decorator.rb +10 -0
  61. data/app/decorators/ahoy_captain/device_decorator.rb +13 -2
  62. data/app/decorators/ahoy_captain/page_decorator.rb +11 -0
  63. data/app/decorators/ahoy_captain/region_decorator.rb +16 -0
  64. data/app/decorators/ahoy_captain/source_decorator.rb +7 -0
  65. data/app/models/ahoy_captain/export.rb +48 -0
  66. data/app/presenters/ahoy_captain/dashboard_presenter.rb +24 -16
  67. data/app/presenters/ahoy_captain/funnel_presenter.rb +32 -29
  68. data/app/presenters/ahoy_captain/goals_presenter.rb +32 -23
  69. data/app/queries/ahoy_captain/application_query.rb +9 -5
  70. data/app/queries/ahoy_captain/campaign_query.rb +14 -0
  71. data/app/queries/ahoy_captain/city_query.rb +11 -0
  72. data/app/queries/ahoy_captain/country_query.rb +10 -0
  73. data/app/queries/ahoy_captain/device_query.rb +10 -0
  74. data/app/queries/ahoy_captain/entry_pages_query.rb +3 -2
  75. data/app/queries/ahoy_captain/event_query.rb +26 -9
  76. data/app/queries/ahoy_captain/exit_pages_query.rb +6 -4
  77. data/app/queries/ahoy_captain/region_query.rb +11 -0
  78. data/app/queries/ahoy_captain/source_query.rb +10 -0
  79. data/app/queries/ahoy_captain/stats/average_visit_duration_query.rb +3 -7
  80. data/app/queries/ahoy_captain/stats/bounce_rates_query.rb +9 -6
  81. data/app/queries/ahoy_captain/stats/total_visitors_query.rb +1 -1
  82. data/app/queries/ahoy_captain/stats/unique_visitors_query.rb +1 -1
  83. data/app/queries/ahoy_captain/stats/views_per_visit_query.rb +2 -2
  84. data/app/queries/ahoy_captain/stats/visit_duration_query.rb +4 -4
  85. data/app/queries/ahoy_captain/top_page_query.rb +13 -0
  86. data/app/queries/ahoy_captain/visit_query.rb +12 -12
  87. data/app/views/ahoy_captain/devices/_table.html.erb +5 -0
  88. data/app/views/ahoy_captain/devices/index.html+details.erb +1 -1
  89. data/app/views/ahoy_captain/devices/index.html.erb +2 -2
  90. data/app/views/ahoy_captain/goals/index.html.erb +3 -35
  91. data/app/views/ahoy_captain/layouts/application.html.erb +1 -1
  92. data/app/views/ahoy_captain/roots/show.html.erb +81 -73
  93. data/app/views/ahoy_captain/stats/base/index.html.erb +8 -1
  94. data/app/views/ahoy_captain/stats/show.html.erb +1 -1
  95. data/config/routes.rb +7 -0
  96. data/lib/ahoy_captain/ahoy/event_methods.rb +29 -75
  97. data/lib/ahoy_captain/configuration.rb +2 -2
  98. data/lib/ahoy_captain/engine.rb +1 -0
  99. data/lib/ahoy_captain/goals.rb +15 -3
  100. data/lib/ahoy_captain/period_collection.rb +1 -1
  101. data/lib/ahoy_captain/version.rb +1 -1
  102. data/lib/generators/ahoy_captain/migration_generator.rb +21 -0
  103. data/lib/generators/ahoy_captain/templates/config.rb.tt +18 -3
  104. data/lib/generators/ahoy_captain/templates/migration.rb.tt +7 -0
  105. metadata +48 -7
  106. data/app/assets/javascript/ahoy_captain/controllers/filter_controller.js +0 -145
  107. data/app/assets/javascript/ahoy_captain/controllers/link_controller.js +0 -43
  108. data/app/assets/javascript/ahoy_captain/controllers/navigation_controller.js +0 -25
  109. data/app/models/ahoy_captain/current.rb +0 -9
  110. data/app/models/ahoy_captain/url_helpers.rb +0 -6
@@ -1,44 +1,11 @@
1
1
  <div class="flex flex-col min-h-[380px] w-full pt-4">
2
- <div class="flex text-sm font-bold text-base-content mb-4">
3
- <span class="grow"><%= category_name %></span>
4
- <span ><%= unit_name %></span>
5
- <% if additional_cols.include?(:percent_total) %>
6
- <span class="w-8 ml-8 text-right">%</span>
7
- <% end %>
8
- <% if additional_cols.include?(:total) %>
9
- <span class="w-8 ml-8 text-right">Total</span>
10
- <% end %>
11
- <% if additional_cols.include?(:conversion_rate) %>
12
- <span class="w-8 ml-8 text-right">CR</span>
13
- <% end %>
14
- </div>
2
+ <%= render @header %>
15
3
  <div class='min-h-[420px]'>
16
4
  <div class="grow">
17
5
  <% if items.respond_to?(:each) && items.any? %>
18
6
  <% items.each do |item| %>
19
7
  <div class='leading-10 flex relative'>
20
- <progress class='progress-primary bg-base-100 h-8 grow' value="<%= item.unit_amount %>" max="<%= max_amount %>">
21
- </progress>
22
- <span class="grow text-elipsis overflow-hidden absolute left-4 bottom-3 h-8 text-base-content">
23
- <%= item.display_name %>
24
- </span>
25
- <span class="w-8 ml-8 text-right">
26
- <%= render AhoyCaptain::TooltipComponent.new(amount: item.unit_amount) %>
27
- </span>
28
-
29
- <% if additional_cols.include?(:percent_total) %>
30
- <span class="w-8 ml-8 text-right"><%= percent_total(item) %></span>
31
- <% end %>
32
- <% if additional_cols.include?(:total) %>
33
- <span class="w-8 ml-8 text-right">
34
- <%= render AhoyCaptain::TooltipComponent.new(amount: item.total) %>
35
- </span>
36
- <% end %>
37
- <% if additional_cols.include?(:conversion_rate) %>
38
- <span class="w-8 ml-8 text-right">
39
- <%= item.conversion_rate * 100.0 %>%
40
- </span>
41
- <% end %>
8
+ <%= render_row(item) %>
42
9
  </div>
43
10
  <% end %>
44
11
  <% else %>
@@ -1,13 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class AhoyCaptain::TableComponent < ViewComponent::Base
4
- SUPPORTED_COLS = [:percent_total, :total, :conversion_rate]
4
+ DEFAULT_HEADER = AhoyCaptain::Tables::Headers::HeaderComponent
5
+ DEFAULT_ROW = AhoyCaptain::Tables::Rows::RowComponent
5
6
 
6
- def initialize(items:, category_name:, unit_name:, additional_cols: [])
7
+ def initialize(items:, category_name: nil, unit_name: nil, header: nil, row: DEFAULT_ROW)
7
8
  @items = items
8
9
  @category_name = category_name
9
10
  @unit_name = unit_name
10
11
  @additional_cols = additional_cols
12
+ if header.nil?
13
+ @header = DEFAULT_HEADER.new(category_name: category_name, unit_name: unit_name)
14
+ else
15
+ @header = header.new
16
+ end
17
+ @row = row
18
+ end
19
+
20
+ def render_row(item)
21
+ @row.new(table: self, item: item).render_in(view_context)
11
22
  end
12
23
 
13
24
  private
@@ -22,7 +33,4 @@ class AhoyCaptain::TableComponent < ViewComponent::Base
22
33
  @total ||= items.first.total_count
23
34
  end
24
35
 
25
- def percent_total(item)
26
- '%.1f' % ((item.unit_amount.to_i * 1.0 / total)*100.0)
27
- end
28
36
  end
@@ -0,0 +1,3 @@
1
+ <%= render ::AhoyCaptain::Tables::Headers::HeaderComponent.new(category_name: "Device", unit_name: "Visitors") do %>
2
+ <span class="w-8 ml-8 text-right">%</span>
3
+ <% end %>
@@ -0,0 +1,9 @@
1
+ module AhoyCaptain
2
+ module Tables
3
+ module Headers
4
+ class DevicesHeaderComponent < ViewComponent::Base
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ <div class="flex text-sm font-bold text-base-content mb-4">
2
+ <span class="grow">Goal</span>
3
+ <span class="w-8 ml-8 text-right">Uniques</span>
4
+ <span class="w-8 ml-8 text-right">Total</span>
5
+ <span class="w-8 ml-8 text-right">CR</span>
6
+ </div>
@@ -0,0 +1,9 @@
1
+ module AhoyCaptain
2
+ module Tables
3
+ module Headers
4
+ class GoalsHeaderComponent < ViewComponent::Base
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ <div class="flex text-sm font-bold text-base-content mb-4">
2
+ <span class="grow"><%= @category_name %></span>
3
+ <span ><%= @unit_name %></span>
4
+ <%= content %>
5
+ </div>
@@ -0,0 +1,12 @@
1
+ module AhoyCaptain
2
+ module Tables
3
+ module Headers
4
+ class HeaderComponent < ViewComponent::Base
5
+ def initialize(category_name:, unit_name:)
6
+ @category_name = category_name
7
+ @unit_name = unit_name
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ <%= progress_bar(@item.count, @item.total_count, @item.display_name) %>
2
+ <%= item do %>
3
+ <%= tooltip(@item.count) %>
4
+ <% end %>
5
+ <%= item(percent_total(@item)) %>
@@ -0,0 +1,12 @@
1
+ module AhoyCaptain
2
+ module Tables
3
+ module Rows
4
+ class DevicesRowComponent < RowComponent
5
+
6
+ def total
7
+ @item.total_count
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ <%= progress_bar(@item.cr, 100, @item.name) %>
2
+ <%= item do %>
3
+ <%= tooltip(@item.unique_visits) %>
4
+ <% end %>
5
+ <%= item do %>
6
+ <%= tooltip(@item.total_events) %>
7
+ <% end %>
8
+ <%= item do %>
9
+ <%= tooltip(@item.cr) %>
10
+ <% end %>
11
+
@@ -0,0 +1,12 @@
1
+ module AhoyCaptain
2
+ module Tables
3
+ module Rows
4
+ class GoalsRowComponent < RowComponent
5
+
6
+ def total
7
+ @item.total_count
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ <%= progress_bar(@item.count, @item.total_count, @item.display_name) %>
2
+ <%=
3
+ item do
4
+ tooltip(@item.count)
5
+ end
6
+ %>
@@ -0,0 +1,41 @@
1
+ module AhoyCaptain
2
+ module Tables
3
+ module Rows
4
+ class RowComponent < ViewComponent::Base
5
+ def initialize(table:, item:)
6
+ @table = table
7
+ @item = item
8
+ end
9
+
10
+ def progress_bar(value, max, label)
11
+ items = []
12
+ items << view_context.content_tag(:progress, "", class: "progress-primary bg-base-100 h-8 grow", value: value, max: max)
13
+ items << view_context.content_tag(:span, class: "grow text-elipsis overflow-hidden absolute left-4 bottom-3 h-8 text-base-content") do
14
+ label
15
+ end
16
+
17
+ items.join.html_safe
18
+ end
19
+
20
+ def item(value = nil, &block)
21
+ view_context.content_tag(:span, class: "w-8 ml-8 text-right") do
22
+ if value
23
+ value
24
+ else
25
+ capture(&block)
26
+ end
27
+ end
28
+ end
29
+
30
+
31
+ def percent_total(item)
32
+ '%.1f' % ((item.unit_amount.to_i * 1.0 / total)*100.0)
33
+ end
34
+
35
+ def tooltip(value)
36
+ AhoyCaptain::TooltipComponent.new(amount: value).render_in(self)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,3 @@
1
-
2
-
3
1
  module AhoyCaptain
4
2
  module Limitable
5
3
  private
@@ -22,19 +20,25 @@ module AhoyCaptain
22
20
 
23
21
  layout 'ahoy_captain/layouts/application'
24
22
 
25
- before_action do
26
- Current.request = self
23
+ # show the details frame
24
+ before_action :use_details_frame
25
+
26
+ # act like an spa without being an spa
27
+ before_action :act_like_an_spa
28
+
29
+ rescue_from Widget::WidgetDisabled do |e|
30
+ render template: 'ahoy_captain/shared/widget_disabled', locals: { frame: e.frame }
27
31
  end
28
32
 
29
- # show the details frame
30
- before_action do
33
+ private
34
+
35
+ def use_details_frame
31
36
  if request.headers['Turbo-Frame'] == 'details'
32
37
  request.variant = :details
33
38
  end
34
39
  end
35
40
 
36
- # act like an spa without being an spa
37
- before_action do
41
+ def act_like_an_spa
38
42
  if request.format.html? && request.headers['Turbo-Frame'].blank?
39
43
  if request.path != root_path
40
44
  requested_params = Rails.application.routes.recognize_path(request.path).except(:controller, :action)
@@ -46,12 +50,6 @@ module AhoyCaptain
46
50
  end
47
51
  end
48
52
 
49
- rescue_from Widget::WidgetDisabled do |e|
50
- render template: 'ahoy_captain/shared/widget_disabled', locals: { frame: e.frame }
51
- end
52
-
53
- private
54
-
55
53
  def visit_query
56
54
  VisitQuery.call(params)
57
55
  end
@@ -60,6 +58,7 @@ module AhoyCaptain
60
58
  EventQuery.call(params)
61
59
  end
62
60
 
61
+ # Only paginate details requests requests
63
62
  def paginate(collection)
64
63
  if paginate?
65
64
  pagy, results = pagy(collection, page: params[:page])
@@ -75,7 +74,10 @@ module AhoyCaptain
75
74
  end
76
75
 
77
76
  def cached(*names)
78
- AhoyCaptain.cache.fetch("ahoy_captain:#{names.join(":")}:#{request.query_parameters.sort.map { |k,v| "#{k}-#{v}" }.join(":")}", expire_in: AhoyCaptain.config.cache.ttl) do
77
+ if AhoyCaptain.cache.class == ActiveSupport::Cache::NullStore
78
+ return yield
79
+ end
80
+ AhoyCaptain.cache.fetch("ahoy_captain:#{names.join(":")}:#{request.query_parameters.sort.map { |k,v| "#{k}-#{v}" }.join(":")}", expire_in: AhoyCaptain.config.cache[:ttl]) do
79
81
  yield
80
82
  end
81
83
  end
@@ -10,17 +10,9 @@ module AhoyCaptain
10
10
 
11
11
  def index
12
12
  results = cached(:campaigns, params[:campaigns_type]) do
13
- visit_query
14
- .select(
15
- "COALESCE(#{params[:campaigns_type]}, 'Direct/None') as label",
16
- "count(COALESCE(#{params[:campaigns_type]}, 'Direct/None')) as count",
17
- "sum(count(COALESCE(#{params[:campaigns_type]}, 'Direct/None'))) OVER() as total_count"
18
- )
19
- .group("COALESCE(#{params[:campaigns_type]}, 'Direct/None')")
20
- .order(Arel.sql("count(COALESCE(#{params[:campaigns_type]}, 'Direct/None')) desc"))
21
- .limit(limit)
13
+ CampaignQuery.call(params).limit(limit)
22
14
  end
23
- @campaigns = paginate(results).map { |campaign| CampaignDecorator.new(campaign) }
15
+ @campaigns = paginate(results).map { |campaign| CampaignDecorator.new(campaign, self) }
24
16
  @campaign_type = params[:campaigns_type]&.titleize&.gsub("Utm", "UTM")
25
17
  end
26
18
  end
@@ -10,15 +10,11 @@ module AhoyCaptain
10
10
 
11
11
  def index
12
12
  results = cached(:cities) do
13
- visit_query
14
- .select("city, country, count(concat(city, region, country)) as count, sum(count(concat(city, region, country))) over() as total_count")
15
- .where.not(city: nil)
16
- .group("city, region, country")
17
- .order(Arel.sql "count(concat(city, region, country)) desc")
13
+ CityQuery.call(params)
18
14
  .limit(limit)
19
15
  end
20
16
 
21
- @cities = paginate(results).map { |city| CityDecorator.new(city) }
17
+ @cities = paginate(results).map { |city| CityDecorator.new(city, self) }
22
18
  end
23
19
  end
24
20
  end
@@ -1,7 +1,6 @@
1
1
  module AhoyCaptain
2
2
  class CountriesController < ApplicationController
3
3
  include Limitable
4
- include Rangeable
5
4
 
6
5
  before_action do
7
6
  if Widget.disabled?(:locations, :countries)
@@ -11,14 +10,11 @@ module AhoyCaptain
11
10
 
12
11
  def index
13
12
  results = cached(:countries) do
14
- visit_query
15
- .reselect("country as label, count(country) as count, sum(count(country)) OVER() as total_count")
16
- .group("country")
17
- .order("count(country) desc")
13
+ CountryQuery.call(params)
18
14
  .limit(limit)
19
15
  end
20
16
 
21
- @countries = paginate(results).map { |country| CountryDecorator.new(country) }
17
+ @countries = paginate(results).map { |country| CountryDecorator.new(country, self) }
22
18
  end
23
19
  end
24
20
  end
@@ -10,14 +10,11 @@ module AhoyCaptain
10
10
 
11
11
  def index
12
12
  results = cached(:devices, params[:devices_type]) do
13
- visit_query
14
- .select("#{params[:devices_type]} as label", "count(#{params[:devices_type]}) as count", "sum(count(#{params[:devices_type]})) over() as total_count")
15
- .group(params[:devices_type])
16
- .order("count(#{params[:devices_type]}) desc")
17
- .limit(limit)
13
+ DeviceQuery.call(params)
14
+ .limit(limit)
18
15
  end
19
16
 
20
- @devices = results.map { |device| DeviceDecorator.new(device) }
17
+ @devices = results.map { |device| DeviceDecorator.new(device, self) }
21
18
  end
22
19
  end
23
20
  end
@@ -10,12 +10,10 @@ module AhoyCaptain
10
10
 
11
11
  def index
12
12
  results = cached(:entry_pages) do
13
- EntryPagesQuery.call(params, event_query)
14
- .order(Arel.sql "count(#{AhoyCaptain.config.event[:url_column]}) desc")
15
- .limit(limit)
13
+ EntryPagesQuery.call(params).limit(limit)
16
14
  end
17
15
 
18
- @pages = paginate(results).map { |page| EntryPageDecorator.new(page) }
16
+ @pages = paginate(results).map { |page| EntryPageDecorator.new(page, self) }
19
17
  end
20
18
  end
21
19
  end
@@ -10,11 +10,10 @@ module AhoyCaptain
10
10
 
11
11
  def index
12
12
  results = cached(:exit_pages) do
13
- ExitPagesQuery.call(params, event_query)
14
- .order(Arel.sql "count(#{AhoyCaptain.config.event[:url_column]}) desc")
15
- .limit(limit)
13
+ ExitPagesQuery.call(params)
14
+ .limit(limit)
16
15
  end
17
- @pages = paginate(results).map { |page| ExitPageDecorator.new(page) }
16
+ @pages = paginate(results).map { |page| ExitPageDecorator.new(page, self) }
18
17
  end
19
18
  end
20
19
  end
@@ -0,0 +1,15 @@
1
+ module AhoyCaptain
2
+ class ExportsController < ApplicationController
3
+ skip_before_action :act_like_an_spa
4
+ include Rangeable
5
+
6
+ def show
7
+ export = Export.new(params, self).build
8
+ file = export.to_zip
9
+ send_data file.read,
10
+ type: 'application/zip',
11
+ disposition: 'attachment',
12
+ filename: "AhoyCaptain export #{request.host} #{range[0].to_date} to #{range[1].to_date}.zip"
13
+ end
14
+ end
15
+ end
@@ -1,10 +1,10 @@
1
1
  module AhoyCaptain
2
2
  module Filters
3
3
  module Pages
4
- # TODO: ACCOMODATE EXIT_PAGES
5
4
  class EntryPagesController < BaseController
6
5
  def index
7
- query = event_query.all.with_url.distinct_url
6
+ query = event_query.all.distinct("entry_pages.url").select("entry_pages.url as url")
7
+
8
8
  render json: query.map { |row| { text: row.url } }
9
9
  end
10
10
 
@@ -1,10 +1,9 @@
1
1
  module AhoyCaptain
2
2
  module Filters
3
3
  module Pages
4
- # TODO: ACCOMODATE ENTRY_PAGES
5
4
  class ExitPagesController < BaseController
6
5
  def index
7
- query = event_query.with_url.distinct_url
6
+ query = event_query.distinct("exit_pages.url").select("exit_pages.url as url")
8
7
 
9
8
  render json: query.map { |row| { text: row.url } }
10
9
  end
@@ -0,0 +1,11 @@
1
+ module AhoyCaptain
2
+ module Filters
3
+ module Properties
4
+ class NamesController < BaseController
5
+ def index
6
+ render json: ::Ahoy::Event.select("jsonb_object_keys(properties) as keys").distinct("jsonb_object_keys(properties)").map(&:keys).map { |key| serialize(key) }
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module AhoyCaptain
2
+ module Filters
3
+ module Properties
4
+ class ValuesController < BaseController
5
+ def index
6
+ query = ::Ahoy::Event.with(elements: event_query.select("ahoy_events.properties->>'controller' as element"))
7
+ .select("distinct elements.element").from("elements")
8
+
9
+
10
+ render json: query.map(&:element).map { |element| serialize(element) }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,7 +1,7 @@
1
1
  module AhoyCaptain
2
2
  class RealtimesController < ApplicationController
3
3
  def show
4
- @total = event_query.where('time > ?', 1.minute.ago).distinct(:visit_id).count(:visit_id)
4
+ @total = event_query.where("#{AhoyCaptain.event.table_name}.time > ?", 1.minute.ago).distinct(:visit_id).count(:visit_id)
5
5
  end
6
6
  end
7
7
  end
@@ -10,15 +10,11 @@ module AhoyCaptain
10
10
 
11
11
  def index
12
12
  results = cached(:regions) do
13
- visit_query
14
- .reselect("region, country, count(concat(region, country)) as count, sum(count(region)) over() as total_count")
15
- .where.not(region: nil)
16
- .group("region, country")
17
- .order(Arel.sql "count(concat(region, country)) desc")
18
- .limit(limit)
13
+ RegionQuery.call(params)
14
+ .limit(limit)
19
15
  end
20
16
 
21
- @regions = paginate(results).map { |region| RegionDecorator.new(region) }
17
+ @regions = paginate(results).map { |region| RegionDecorator.new(region, self) }
22
18
  end
23
19
  end
24
20
  end
@@ -10,14 +10,11 @@ module AhoyCaptain
10
10
 
11
11
  def index
12
12
  results = cached(:sources) do
13
- visit_query.select("substring(referring_domain from '(?:.*://)?(?:www\.)?([^/?]*)') as referring_domain, count(substring(referring_domain from '(?:.*://)?(?:www\.)?([^/?]*)')) as count, sum(count(substring(referring_domain from '(?:.*://)?(?:www\.)?([^/?]*)'))) OVER() as total_count")
14
- .where.not(referring_domain: nil)
15
- .group("substring(referring_domain from '(?:.*://)?(?:www\.)?([^/?]*)')")
16
- .order(Arel.sql "count(substring(referring_domain from '(?:.*://)?(?:www\.)?([^/?]*)')) desc")
13
+ SourceQuery.call(params)
17
14
  .limit(limit)
18
15
  end
19
16
 
20
- @sources = paginate(results).map { |source| AhoyCaptain::SourceDecorator.new(source) }
17
+ @sources = paginate(results).map { |source| AhoyCaptain::SourceDecorator.new(source, self) }
21
18
  end
22
19
  end
23
20
 
@@ -1,6 +1,67 @@
1
1
  module AhoyCaptain
2
2
  module Stats
3
3
  class BaseController < ApplicationController
4
+ include Rangeable
5
+
6
+ INTERVAL_PERIOD = {
7
+ "realtime" => ["minute"],
8
+ "day" => ["minute", "hour"],
9
+ "7d" => ["hour", "day"],
10
+ "month" => ["day", "week"],
11
+ "all" => ["day", "week", "month"]
12
+ }
13
+
14
+ INTERVALS = ["minute", "hour", "day", "week", "month"]
15
+ private
16
+
17
+ helper_method :selected_interval
18
+ def selected_interval
19
+ if params[:interval].in?(INTERVALS)
20
+ params[:interval]
21
+ else
22
+ default_interval_for_period
23
+ end
24
+ end
25
+
26
+ def default_interval_for_period
27
+ default_interval_for_date_range(range)
28
+ end
29
+
30
+ def default_interval_for_date_range(range)
31
+ if range[1].nil?
32
+ # assume we're in a realtime
33
+ return INTERVAL_PERIOD["realtime"][0]
34
+ end
35
+ diff = (range[1] - range[0]).seconds
36
+ if diff.in_months > 1
37
+ "month"
38
+ elsif diff.in_days > 0
39
+ "day"
40
+ else
41
+ "hour"
42
+ end
43
+ end
44
+
45
+ helper_method :available_intervals
46
+ def available_intervals
47
+ if range
48
+ return INTERVAL_PERIOD["realtime"] if range[1].nil?
49
+
50
+ diff = (range[1] - range[0]).seconds.in_days
51
+
52
+ if diff == 0
53
+ INTERVAL_PERIOD["day"]
54
+ elsif diff <= 7
55
+ INTERVAL_PERIOD["7d"]
56
+ elsif diff <= 31
57
+ INTERVAL_PERIOD["month"]
58
+ else
59
+ INTERVAL_PERIOD["all"]
60
+ end
61
+ else
62
+ INTERVAL_PERIOD["month"]
63
+ end
64
+ end
4
65
  end
5
66
  end
6
67
  end
@@ -1,9 +1,11 @@
1
1
  module AhoyCaptain
2
2
  module Stats
3
3
  class BounceRatesController < BaseController
4
+ # @todo: this is lazy
4
5
  def index
5
- @stats = AhoyCaptain::Stats::BounceRatesQuery.call(params).group_by_day("ahoy_visits.started_at").average("num_events * 100")
6
- #.group_by_day("ahoy_visits.started_at").average("total_events")
6
+ @stats = AhoyCaptain::Stats::BounceRatesQuery.call(params)
7
+ @stats = @stats.group_by_period(selected_interval, "daily_bounce_rate.date").average("bounce_rate")
8
+ @label = "Bounce Rate"
7
9
  end
8
10
  end
9
11
  end
@@ -2,7 +2,8 @@ module AhoyCaptain
2
2
  module Stats
3
3
  class TotalPageviewsController < BaseController
4
4
  def index
5
- @stats = AhoyCaptain::Stats::TotalPageviewsQuery.call(params).group_by_day(:time).count
5
+ @stats = AhoyCaptain::Stats::TotalPageviewsQuery.call(params).group_by_period(selected_interval, :time).count
6
+ @label = "Visitors"
6
7
  end
7
8
  end
8
9
  end
@@ -2,7 +2,8 @@ module AhoyCaptain
2
2
  module Stats
3
3
  class TotalVisitsController < BaseController
4
4
  def index
5
- @stats = AhoyCaptain::Stats::TotalVisitorsQuery.call(params).group_by_day(:started_at).count
5
+ @stats = AhoyCaptain::Stats::TotalVisitorsQuery.call(params).group_by_period(selected_interval, :started_at).count
6
+ @label = "Visitors"
6
7
  end
7
8
  end
8
9
  end
@@ -2,7 +2,8 @@ module AhoyCaptain
2
2
  module Stats
3
3
  class UniqueVisitorsController < BaseController
4
4
  def index
5
- @stats = AhoyCaptain::Stats::UniqueVisitorsQuery.call(params).group_by_day(:started_at).count
5
+ @stats = AhoyCaptain::Stats::UniqueVisitorsQuery.call(params).group_by_period(selected_interval, :started_at).count
6
+ @label = "Visitors"
6
7
  end
7
8
  end
8
9
  end