ahoy_captain 0.81 → 0.82

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascript/ahoy_captain/controllers/active_links_controller.js +15 -0
  3. data/app/assets/javascript/ahoy_captain/controllers/filter_form_controller.js +13 -0
  4. data/app/assets/javascript/ahoy_captain/controllers/line_chart_controller.js +37 -0
  5. data/app/assets/javascript/ahoy_captain/controllers/predicate_select_controller.js +10 -0
  6. data/app/assets/javascript/ahoy_captain/controllers/realtime_controller.js +2 -0
  7. data/app/assets/javascript/ahoy_captain/controllers/search_select_controller.js +65 -0
  8. data/app/components/ahoy_captain/filter/modal_component.html.erb +6 -5
  9. data/app/components/ahoy_captain/filter/select_component.html.erb +13 -11
  10. data/app/components/ahoy_captain/filter/select_component.rb +21 -4
  11. data/app/components/ahoy_captain/table_component.html.erb +2 -35
  12. data/app/components/ahoy_captain/table_component.rb +13 -5
  13. data/app/components/ahoy_captain/tables/headers/devices_header_component.html.erb +3 -0
  14. data/app/components/ahoy_captain/tables/headers/devices_header_component.rb +9 -0
  15. data/app/components/ahoy_captain/tables/headers/goals_header_component.html.erb +6 -0
  16. data/app/components/ahoy_captain/tables/headers/goals_header_component.rb +9 -0
  17. data/app/components/ahoy_captain/tables/headers/header_component.html.erb +5 -0
  18. data/app/components/ahoy_captain/tables/headers/header_component.rb +12 -0
  19. data/app/components/ahoy_captain/tables/rows/devices_row_component.html.erb +5 -0
  20. data/app/components/ahoy_captain/tables/rows/devices_row_component.rb +12 -0
  21. data/app/components/ahoy_captain/tables/rows/goals_row_component.html.erb +11 -0
  22. data/app/components/ahoy_captain/tables/rows/goals_row_component.rb +12 -0
  23. data/app/components/ahoy_captain/tables/rows/row_component.html.erb +6 -0
  24. data/app/components/ahoy_captain/tables/rows/row_component.rb +41 -0
  25. data/app/controllers/ahoy_captain/filters/pages/entry_pages_controller.rb +2 -2
  26. data/app/controllers/ahoy_captain/filters/pages/exit_pages_controller.rb +1 -2
  27. data/app/controllers/ahoy_captain/filters/properties/names_controller.rb +11 -0
  28. data/app/controllers/ahoy_captain/filters/properties/values_controller.rb +15 -0
  29. data/app/controllers/ahoy_captain/stats/bounce_rates_controller.rb +1 -0
  30. data/app/controllers/ahoy_captain/stats/total_pageviews_controller.rb +1 -0
  31. data/app/controllers/ahoy_captain/stats/total_visits_controller.rb +1 -0
  32. data/app/controllers/ahoy_captain/stats/unique_visitors_controller.rb +1 -0
  33. data/app/controllers/ahoy_captain/stats/views_per_visits_controller.rb +3 -0
  34. data/app/controllers/ahoy_captain/stats/visit_durations_controller.rb +1 -0
  35. data/app/queries/ahoy_captain/application_query.rb +4 -3
  36. data/app/queries/ahoy_captain/event_query.rb +17 -15
  37. data/app/queries/ahoy_captain/visit_query.rb +1 -1
  38. data/app/views/ahoy_captain/devices/_table.html.erb +5 -0
  39. data/app/views/ahoy_captain/devices/index.html+details.erb +1 -1
  40. data/app/views/ahoy_captain/devices/index.html.erb +2 -2
  41. data/app/views/ahoy_captain/goals/index.html.erb +3 -35
  42. data/app/views/ahoy_captain/layouts/application.html.erb +1 -1
  43. data/app/views/ahoy_captain/roots/show.html.erb +81 -73
  44. data/app/views/ahoy_captain/stats/base/index.html.erb +2 -3
  45. data/config/routes.rb +6 -0
  46. data/lib/ahoy_captain/ahoy/event_methods.rb +28 -74
  47. data/lib/ahoy_captain/goals.rb +9 -1
  48. data/lib/ahoy_captain/version.rb +1 -1
  49. metadata +21 -18
  50. data/app/assets/javascript/ahoy_captain/controllers/filter_controller.js +0 -151
  51. data/app/assets/javascript/ahoy_captain/controllers/link_controller.js +0 -43
  52. data/app/assets/javascript/ahoy_captain/controllers/navigation_controller.js +0 -25
@@ -3,33 +3,35 @@ module AhoyCaptain
3
3
  include Rangeable
4
4
 
5
5
  def build
6
- shared_context = Ransack::Context.for(AhoyCaptain.event)
6
+ entry_pages = ransack_params_for(:event).select { |k,v| k.start_with?("entry_page") }
7
+ exit_pages = ransack_params_for(:event).select { |k,v| k.start_with?("exit_page") }
7
8
 
8
- search_parents = AhoyCaptain.event.ransack(
9
+ event = AhoyCaptain.event
10
+ shared_context = Ransack::Context.for(event)
11
+
12
+ search_parents = event.ransack(
9
13
  ransack_params_for(:event).reject { |k,v| k.start_with?("visit_") }, context: shared_context
10
14
  )
15
+
16
+ visit_params = ransack_params_for(:visit).reject { |k,v| k.start_with?("event_") || k.start_with?("events_") }.transform_keys { |key| "visit_#{key}" }
11
17
  search_children = AhoyCaptain.visit.ransack(
12
- ransack_params_for(:visit).reject { |k,v| k.start_with?("event_") }.transform_keys { |key| "visit_#{key}" }, context: shared_context
18
+ visit_params, context: shared_context
13
19
  )
14
-
15
20
  shared_conditions = [search_parents, search_children].map { |search|
16
21
  Ransack::Visitor.new.accept(search.base)
17
22
  }
18
23
 
19
- AhoyCaptain.event.joins(shared_context.join_sources)
20
- .where(shared_conditions.reduce(&:or))
24
+ joined = AhoyCaptain.event.joins(shared_context.join_sources)
21
25
 
22
- end
26
+ if entry_pages.values.any?(&:present?) || params[:controller].include?("entry_pages")
27
+ joined = joined.with_entry_pages
28
+ end
23
29
 
24
- def within_range
25
- self
26
- end
27
-
28
- def with_visit
29
- @query = @query.joins(:visit)
30
+ if exit_pages.values.any?(&:present?) || params[:controller].include?("exit_pages")
31
+ joined = joined.with_exit_pages
32
+ end
30
33
 
31
- self
34
+ joined.where(shared_conditions.reduce(&:and))
32
35
  end
33
-
34
36
  end
35
37
  end
@@ -17,7 +17,7 @@ module AhoyCaptain
17
17
  }
18
18
 
19
19
  AhoyCaptain.visit.joins(shared_context.join_sources)
20
- .where(shared_conditions.reduce(&:or))
20
+ .where(shared_conditions.reduce(&:and))
21
21
 
22
22
  end
23
23
 
@@ -0,0 +1,5 @@
1
+ <%= render AhoyCaptain::TableComponent.new(items: @devices,
2
+ category_name: 'Devices',
3
+ unit_name: 'Visitors',
4
+ header: ::AhoyCaptain::Tables::Headers::DevicesHeaderComponent,
5
+ row: ::AhoyCaptain::Tables::Rows::DevicesRowComponent) %>
@@ -1,4 +1,4 @@
1
1
  <%= turbo_frame_tag :details do %>
2
- <%= render AhoyCaptain::TableComponent.new(items: @devices, category_name: 'Devices', unit_name: 'Visitors', additional_cols: [:percent_total]) %>
2
+ <%= render 'table' %>
3
3
  <span class="flex justify-center"><%= render_pagination %></span>
4
4
  <% end %>
@@ -1,3 +1,3 @@
1
1
  <%= turbo_frame_tag :devices do %>
2
- <%= render AhoyCaptain::TableComponent.new(items: @devices, category_name: 'Devices', unit_name: 'Visitors', additional_cols: [:percent_total]) %>
3
- <% end %>
2
+ <%= render 'table' %>
3
+ <% end %>
@@ -1,39 +1,7 @@
1
1
  <%= turbo_frame_tag :goals do %>
2
- <div class="flex flex-col min-h-[380px] w-full pt-4">
3
- <div class="flex text-sm font-bold text-base-content mb-4">
4
- <span class="grow">Goal</span>
5
- <span >Uniques</span>
6
- <span class="w-8 ml-8 text-right">Total</span>
7
- <span class="w-8 ml-8 text-right">CR</span>
8
- </div>
9
- <div class='min-h-[420px]'>
10
- <div class="grow">
11
- <% if @presenter.goals.respond_to?(:each) && @presenter.goals.any? %>
12
- <% @presenter.goals.each do |item| %>
13
-
14
- <div class='leading-10 flex relative'>
15
- <progress class='progress-primary bg-base-100 h-8 grow' value="<%= item.cr %>" max="100">
16
- </progress>
17
- <span class="grow text-elipsis overflow-hidden absolute left-4 bottom-3 h-8 text-base-content">
18
- <%= item.name %>
19
- </span>
20
- <span class="w-8 ml-8 text-right">
21
- <%= render AhoyCaptain::TooltipComponent.new(amount: item.unique_visits) %>
22
- </span>
23
- <span class="w-8 ml-8 text-right">
24
- <%= render AhoyCaptain::TooltipComponent.new(amount: item.total_events) %>
25
- </span>
26
- <span class="w-8 ml-8 text-right">
27
- <%= item.cr %>%
28
- </span>
29
- </div>
30
- <% end %>
31
- <% else %>
32
- <p>No data found</p>
33
- <% end %>
34
- </div>
35
- </div>
36
- </div>
2
+ <%= render AhoyCaptain::TableComponent.new(items: @presenter.goals,
3
+ header: ::AhoyCaptain::Tables::Headers::GoalsHeaderComponent,
4
+ row: ::AhoyCaptain::Tables::Rows::GoalsRowComponent) %>
37
5
 
38
6
 
39
7
  <% end %>
@@ -116,7 +116,7 @@
116
116
  </style>
117
117
  </head>
118
118
 
119
- <body data-theme='<%= AhoyCaptain.config.theme %>' class='bg-base-300' data-controller='navigation' data-action="filter-tag:remove->navigation#removeQueryParam">
119
+ <body data-theme='<%= AhoyCaptain.config.theme %>' class='bg-base-300' data-controller='application'>
120
120
  <%= yield %>
121
121
  <div class="flex justify-center bg-base-300 border-t-4 border-base-100 py-4">
122
122
  <div class="flex justify-around space-x-4 my-4">
@@ -14,17 +14,17 @@
14
14
 
15
15
  <%= render AhoyCaptain::TileComponent.new(title: 'Top Sources') do |component| %>
16
16
  <% component.with_display_links do %>
17
- <div data-controller='link' data-link-top_sources-class="text-primary underline">
18
- <a href="<%= sources_path(search_params) %>" data-turbo-frame="sources" data-action='click->link#changeTopSources' data-link-target='top_sourcesLink'>All</a>
19
- <%= render AhoyCaptain::DropdownLinkComponent.new(title: "Campaign") do |dropdown| %>
20
- <% %w{utm_source utm_medium utm_term utm_content utm_campaign}.each do |source| %>
21
- <% dropdown.with_option do %>
22
- <a href="<%= public_send("campaign_#{source}_path".to_sym, **search_params) %>" data-turbo-frame="sources" data-action='click->link#changeTopSources' data-link-target='top_sourcesLink'>
23
- <%= source.titleize.gsub("Utm", "UTM") %>
24
- </a>
17
+ <div data-controller="active-links">
18
+ <a href="<%= sources_path(search_params) %>" data-turbo-frame="sources" data-active-links-target="link">All</a>
19
+ <%= render AhoyCaptain::DropdownLinkComponent.new(title: "Campaign") do |dropdown| %>
20
+ <% %w{utm_source utm_medium utm_term utm_content utm_campaign}.each do |source| %>
21
+ <% dropdown.with_option do %>
22
+ <a href="<%= public_send("campaign_#{source}_path".to_sym, **search_params) %>" data-turbo-frame="sources" data-active-links-target="link">
23
+ <%= source.titleize.gsub("Utm", "UTM") %>
24
+ </a>
25
+ <% end %>
25
26
  <% end %>
26
27
  <% end %>
27
- <% end %>
28
28
  </div>
29
29
  <% end %>
30
30
  <% component.with_statistic_display do %>
@@ -37,11 +37,11 @@
37
37
 
38
38
  <%= render AhoyCaptain::TileComponent.new(title: 'Top Pages') do |component| %>
39
39
  <% component.with_display_links do %>
40
- <div data-controller='link' data-link-top_pages-class="text-primary underline" >
41
- <a href="<%= top_pages_path(search_params) %>" data-turbo-frame="pages" data-action='click->link#changeTopPages' data-link-target='top_pagesLink'>Top Pages</a>
42
- <a href="<%= entry_pages_path(search_params) %>" data-turbo-frame="pages" data-action='click->link#changeTopPages' data-link-target='top_pagesLink'>Entry Pages</a>
43
- <a href="<%= exit_pages_path(search_params) %>" data-turbo-frame="pages" data-action='click->link#changeTopPages' data-link-target='top_pagesLink'>Exit Pages</a>
44
- </div>
40
+ <div data-controller='active-links'>
41
+ <a href="<%= top_pages_path(search_params) %>" data-turbo-frame="pages" data-active-links-target="link">Top Pages</a>
42
+ <a href="<%= entry_pages_path(search_params) %>" data-turbo-frame="pages" data-active-links-target="link">Entry Pages</a>
43
+ <a href="<%= exit_pages_path(search_params) %>" data-turbo-frame="pages" data-active-links-target="link">Exit Pages</a>
44
+ </div>
45
45
  <% end %>
46
46
  <% component.with_statistic_display do %>
47
47
  <%= turbo_frame_tag :pages, src: top_pages_path(search_params), loading: :lazy %>
@@ -53,11 +53,11 @@
53
53
 
54
54
  <%= render AhoyCaptain::TileComponent.new(title: 'Countries') do |component| %>
55
55
  <% component.with_display_links do %>
56
- <div data-controller='link' data-link-countries-class="text-primary underline">
57
- <a href="<%= countries_path(search_params) %>" data-turbo-frame="geography" data-action='click->link#changeCountries' data-link-target='countriesLink'>Countries</a>
58
- <a href="<%= regions_path(search_params) %>" data-turbo-frame="geography" data-action='click->link#changeCountries' data-link-target='countriesLink'>Regions</a>
59
- <a href="<%= cities_path(search_params) %>" data-turbo-frame="geography" data-action='click->link#changeCountries' data-link-target='countriesLink'>Cities</a>
60
- </div>
56
+ <div data-controller="active-links">
57
+ <a href="<%= countries_path(search_params) %>" data-turbo-frame="geography" data-active-links-target="link">Countries</a>
58
+ <a href="<%= regions_path(search_params) %>" data-turbo-frame="geography" data-active-links-target="link">Regions</a>
59
+ <a href="<%= cities_path(search_params) %>" data-turbo-frame="geography" data-active-links-target="link">Cities</a>
60
+ </div>
61
61
  <% end %>
62
62
  <% component.with_statistic_display do %>
63
63
  <%= turbo_frame_tag :geography, src: countries_path(search_params), loading: :lazy %>
@@ -69,11 +69,11 @@
69
69
 
70
70
  <%= render AhoyCaptain::TileComponent.new(title: 'Devices') do |component| %>
71
71
  <% component.with_display_links do %>
72
- <div data-controller='link' data-link-devices-class="text-primary underline">
73
- <a href="<%= devices_browsers_path(search_params) %>" data-turbo-frame="devices" data-action='click->link#changeDevices' data-link-target='devicesLink'>Browser</a>
74
- <a href="<%= devices_operating_systems_path(search_params) %>" data-turbo-frame="devices" data-action='click->link#changeDevices' data-link-target='devicesLink'>OS</a>
75
- <a href="<%= devices_device_types_path(search_params) %>" data-turbo-frame="devices" data-action='click->link#changeDevices' data-link-target='devicesLink'>Size</a>
76
- </div>
72
+ <div data-controller="active-links">
73
+ <a href="<%= devices_browsers_path(search_params) %>" data-turbo-frame="devices" data-active-links-target="link">Browser</a>
74
+ <a href="<%= devices_operating_systems_path(search_params) %>" data-turbo-frame="devices" data-active-links-target="link">OS</a>
75
+ <a href="<%= devices_device_types_path(search_params) %>" data-turbo-frame="devices" data-active-links-target="link">Size</a>
76
+ </div>
77
77
  <% end %>
78
78
  <% component.with_statistic_display do %>
79
79
  <%= turbo_frame_tag :devices, src: devices_browsers_path(search_params), loading: :lazy %>
@@ -98,75 +98,83 @@
98
98
  <% end %>
99
99
 
100
100
  <% end %>
101
- <% component.with_statistic_display do %>
101
+ <% component.with_statistic_display do %>
102
102
  <%= turbo_frame_tag :goals, src: goals_path(search_params), loading: :lazy %>
103
103
  <% end %>
104
104
  <% end %>
105
105
  </div>
106
106
  </main>
107
107
 
108
- <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Page", id: "pageModal") do |modal| %>
109
- <% modal.with_modal_display do %>
110
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Action", column: :route, url: filters_actions_path, predicates: [:in, :not_in]) %>
111
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Entry page", column: :entry_page, url: filters_entry_pages_path, predicates: [:in, :not_in]) %>
112
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Exit page", column: :exit_page, url: filters_exit_pages_path, predicates: [:in, :not_in]) %>
108
+ <%= form_with url: url_for(params.permit!.except(:q)), method: :get, data: { turbo_frame: "_top", controller: "filter-form", action: "reset->filter-form#handleReset" } do |form| %>
109
+ <%= form.hidden_field :period, value: params[:period] %>
110
+ <%= form.hidden_field :start_date, value: params[:start_date] %>
111
+ <%= form.hidden_field :end_date, value: params[:end_date] %>
112
+ <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Page", id: "pageModal") do |modal| %>
113
+ <% modal.with_modal_display do %>
114
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Action", column: :route, url: filters_actions_path, predicates: [:in, :not_in], form: form) %>
115
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Entry page", column: :entry_page, url: filters_entry_pages_path, predicates: [:in, :not_in], form: form) %>
116
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Exit page", column: :exit_page, url: filters_exit_pages_path, predicates: [:in, :not_in], form: form) %>
117
+ <% end %>
113
118
  <% end %>
114
- <% end %>
115
119
 
116
- <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Country", id: "countryModal") do |modal| %>
117
- <% modal.with_modal_display do %>
118
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Country", column: :country, url: filters_locations_countries_path, predicates: [:in, :not_in]) %>
119
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Region", column: :region, url: filters_locations_regions_path, predicates: [:in, :not_in]) %>
120
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "City", column: :city, url: filters_locations_cities_path, predicates: [:in, :not_in]) %>
120
+ <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Country", id: "countryModal") do |modal| %>
121
+ <% modal.with_modal_display do %>
122
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Country", column: :country, url: filters_locations_countries_path, predicates: [:in, :not_in], form: form) %>
123
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Region", column: :region, url: filters_locations_regions_path, predicates: [:in, :not_in], form: form) %>
124
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "City", column: :city, url: filters_locations_cities_path, predicates: [:in, :not_in], form: form) %>
125
+ <% end %>
121
126
  <% end %>
122
- <% end %>
123
127
 
124
- <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Source", id: "sourceModal") do |modal| %>
125
- <% modal.with_modal_display do %>
126
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Source", column: :referring_domain, url: filters_sources_path, predicates: [:in, :not_in]) %>
128
+ <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Source", id: "sourceModal") do |modal| %>
129
+ <% modal.with_modal_display do %>
130
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Source", column: :referring_domain, url: filters_sources_path, predicates: [:in, :not_in], form: form) %>
131
+ <% end %>
127
132
  <% end %>
128
- <% end %>
129
133
 
130
- <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Screen size", id: "screenModal") do |modal| %>
131
- <% modal.with_modal_display do %>
132
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Screen size", column: :device_type, url: filters_screens_path, predicates: [:in, :not_in]) %>
134
+ <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Screen size", id: "screenModal") do |modal| %>
135
+ <% modal.with_modal_display do %>
136
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Screen size", column: :device_type, url: filters_screens_path, predicates: [:in, :not_in], form: form) %>
137
+ <% end %>
133
138
  <% end %>
134
- <% end %>
135
139
 
136
- <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Browser", id: "osModal") do |modal| %>
137
- <% modal.with_modal_display do %>
138
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Operating System", column: :os, url: filters_names_path, predicates: [:in, :not_in]) %>
139
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Operating System Version", column: :os_version, url: filters_versions_path, predicates: [:in, :not_in]) %>
140
+ <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Browser", id: "osModal") do |modal| %>
141
+ <% modal.with_modal_display do %>
142
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Operating System", column: :os, url: filters_names_path, predicates: [:in, :not_in], form: form) %>
143
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "Operating System Version", column: :os_version, url: filters_versions_path, predicates: [:in, :not_in], form: form) %>
144
+ <% end %>
140
145
  <% end %>
141
- <% end %>
142
146
 
143
- <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Campaign", id: "utmModal") do |modal| %>
144
- <% modal.with_modal_display do %>
145
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Medium", column: :utm_medium, url: filters_utm_mediums_path, predicates: [:in, :not_in]) %>
146
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Source", column: :utm_source, url: filters_utm_sources_path, predicates: [:in, :not_in]) %>
147
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Campaign", column: :utm_campaign, url: filters_utm_campaigns_path, predicates: [:in, :not_in]) %>
148
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Term", column: :utm_term, url: filters_utm_terms_path, predicates: [:in, :not_in]) %>
149
- <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Content", column: :utm_content, url: filters_utm_contents_path, predicates: [:in, :not_in]) %>
147
+ <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Filter by Campaign", id: "utmModal") do |modal| %>
148
+ <% modal.with_modal_display do %>
149
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Medium", column: :utm_medium, url: filters_utm_mediums_path, predicates: [:in, :not_in], form: form) %>
150
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Source", column: :utm_source, url: filters_utm_sources_path, predicates: [:in, :not_in], form: form) %>
151
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Campaign", column: :utm_campaign, url: filters_utm_campaigns_path, predicates: [:in, :not_in], form: form) %>
152
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Term", column: :utm_term, url: filters_utm_terms_path, predicates: [:in, :not_in], form: form) %>
153
+ <%= render AhoyCaptain::Filter::SelectComponent.new(label: "UTM Content", column: :utm_content, url: filters_utm_contents_path, predicates: [:in, :not_in], form: form) %>
154
+ <% end %>
150
155
  <% end %>
151
- <% end %>
152
156
 
153
- <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Custom Range", id: "customRangeModal") do |modal| %>
154
- <% modal.with_modal_display do %>
155
- <div class="flex gap-2 w-full">
156
- <div class="form-control w-full max-w-xs">
157
- <label class="label">
158
- <span class="label-text">Start Date</span>
159
- </label>
160
- <input type="datetime-local" name="start_date" class="input input-bordered w-full" value="<%= params[:start_date] %>" />
161
- </div>
162
- <div class="form-control w-full max-w-xs">
163
- <label class="label">
164
- <span class="label-text">End Date</span>
165
- </label>
166
- <input type="datetime-local" name="end_date" class="input input-bordered w-full" value="<%= params[:end_date] %>" />
157
+
158
+ <%= render AhoyCaptain::Filter::ModalComponent.new(title: "Custom Range", id: "customRangeModal") do |modal| %>
159
+ <% modal.with_modal_display do %>
160
+ <div class="flex gap-2 w-full">
161
+ <div class="form-control w-full max-w-xs">
162
+ <label class="label" for="start_date">
163
+ <span class="label-text">Start Date</span>
164
+ </label>
165
+ <input type="datetime-local" id="start_date" name="start_date" class="input input-bordered w-full" value="<%= params[:start_date] %>" />
166
+ </div>
167
+ <div class="form-control w-full max-w-xs">
168
+ <label class="label" for="end_date">
169
+ <span class="label-text">End Date</span>
170
+ </label>
171
+ <input type="datetime-local" id="end_date" name="end_date" class="input input-bordered w-full" value="<%= params[:end_date] %>" />
172
+ </div>
167
173
  </div>
168
- </div>
174
+ <% end %>
169
175
  <% end %>
176
+
177
+
170
178
  <% end %>
171
179
 
172
180
  <dialog id="detailsModal" class="modal">
@@ -1,11 +1,10 @@
1
1
  <%= turbo_frame_tag :chart do %>
2
- <div class="flex justify-end gap-3 items-center">
3
- <a href="<%= export_path(request.query_parameters) %>" target="_blank" data-turbo-frame="false">Download</a>
2
+ <div class="flex justify-end ...">
4
3
  <%= form_with url: url_for(params.permit!), method: :get, data: { controller: "interval" } do %>
5
4
  <%= select_tag :interval, options_for_select(available_intervals.collect { |interval| [interval.titleize, interval] }, selected: selected_interval), class: "select select-bordered select-sm w-full max-w-sm", 'data-action': "change->interval#handleChange" %>
6
5
  <% end %>
7
6
  </div>
8
7
  <div>
9
- <%= line_chart @stats %>
8
+ <canvas data-controller="line-chart" data-line-chart-data-value="<%= @stats.to_json %>" data-line-chart-label-value="<%= @label %>"></canvas>
10
9
  </div>
11
10
  <% end %>
data/config/routes.rb CHANGED
@@ -42,6 +42,12 @@ AhoyCaptain::Engine.routes.draw do
42
42
  %w{country region city}.each do |type|
43
43
  get "locations/#{type.pluralize}" => "locations#index", defaults: { type: type }
44
44
  end
45
+
46
+ namespace :properties do
47
+ resources :names, only: [:index]
48
+ resources :values, only: [:index]
49
+ end
50
+
45
51
  resources :sources, only: [:index]
46
52
  resources :screens, only: [:index]
47
53
  scope :operating_systems, module: :operating_systems do
@@ -4,104 +4,58 @@ module AhoyCaptain
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- ransacker :route do |parent|
7
+ ransacker :route do |_parent|
8
8
  Arel.sql(AhoyCaptain.config.event[:url_column])
9
9
  end
10
10
 
11
- scope :with_routes, -> { where(AhoyCaptain.config.event[:url_exists]) }
12
-
13
- scope :with_url, -> {
14
- select(Arel.sql("#{AhoyCaptain.config.event.url_column} AS url"))
15
- }
16
-
17
- scope :distinct_url, -> {
18
- distinct(Arel.sql("#{AhoyCaptain.config.event.url_column}"))
19
- }
20
-
21
- scope :url_in, ->(*args) {
22
- where("#{AhoyCaptain.config.event.url_column} IN (?)", args)
23
- }
24
-
25
- scope :url_eq, ->(arg) {
26
- if arg.is_a?(Array)
27
- arg = arg[0]
28
- end
29
- where("#{AhoyCaptain.config.event.url_column} = ?", arg)
30
- }
31
-
32
- scope :url_not_in, ->(*args) {
33
- where("#{AhoyCaptain.config.event.url_column} NOT IN (?)", args)
34
- }
35
-
36
- scope :url_i_cont, ->(arg) {
37
- where("#{AhoyCaptain.config.event.url_column} ILIKE ?", "%#{arg}%")
38
- }
39
-
40
- scope :route_eq, ->(arg) {
41
- url_eq(arg)
42
- }
43
-
44
- scope :route_in, ->(*args) {
45
- url_in(*args)
46
- }
11
+ ransacker :entry_page do |parent|
12
+ Arel.sql("entry_pages.url")
13
+ end
47
14
 
48
- scope :route_not_in, ->(*args) {
49
- url_not_in(*args)
50
- }
15
+ ransacker :exit_page do |parent|
16
+ Arel.sql("exit_pages.url")
17
+ end
51
18
 
52
- scope :route_i_cont, ->(arg) {
53
- url_i_cont(arg)
19
+ scope :with_entry_pages, -> {
20
+ with(entry_pages: self.select("MIN(ahoy_events.id) as min_id, #{Arel.sql("#{AhoyCaptain.config.event.url_column} AS url")}").where(name: AhoyCaptain.config.event[:view_name]).group("ahoy_events.properties")).joins("INNER JOIN entry_pages ON entry_pages.min_id = ahoy_events.id")
54
21
  }
55
-
56
- scope :entry_page_in, ->(*args) {
57
- table_alias = "first_events_#{SecureRandom.hex.first(6)}"
58
-
59
- subquery = self.select("MIN(id) as min_id").where(name: AhoyCaptain.config.event[:view_name]).route_in(*args).group(:visit_id)
60
- joins("INNER JOIN (#{subquery.to_sql}) #{table_alias} ON #{::AhoyCaptain.event.table_name}.id = #{table_alias}.min_id")
22
+ scope :with_exit_pages, -> {
23
+ with(exit_pages: self.select("MAX(ahoy_events.id) as max_id, #{Arel.sql("#{AhoyCaptain.config.event.url_column} AS url")}").where(name: AhoyCaptain.config.event[:view_name]).group("ahoy_events.properties")).joins("INNER JOIN exit_pages ON exit_pages.max_id = ahoy_events.id")
61
24
  }
62
25
 
63
- scope :entry_page_not_in, ->(*args) {
64
- table_alias = "first_events_#{SecureRandom.hex.first(6)}"
65
- subquery = self.select("MIN(id) as min_id").where(name: AhoyCaptain.config.event[:view_name]).route_not_in(*args).group(:visit_id)
66
- joins("INNER JOIN (#{subquery.to_sql}) #{table_alias} ON #{::AhoyCaptain.event.table_name}.id = #{table_alias}.min_id")
67
- }
26
+ scope :with_routes, -> { where(AhoyCaptain.config.event[:url_exists]) }
68
27
 
69
- scope :entry_page_i_cont, ->(arg) {
70
- table_alias = "first_events_#{SecureRandom.hex.first(6)}"
71
- subquery = self.select("MIN(id) as min_id").where(name: AhoyCaptain.config.event[:view_name]).route_i_cont(arg).group(:visit_id)
72
- joins("INNER JOIN (#{subquery.to_sql}) #{table_alias} ON #{::AhoyCaptain.event.table_name}.id = #{table_alias}.min_id")
28
+ scope :with_url, -> {
29
+ select(Arel.sql("#{AhoyCaptain.config.event.url_column} AS url"))
73
30
  }
74
31
 
75
- scope :exit_page_in, ->(*args) {
76
- table_alias = "last_events_#{SecureRandom.hex.first(6)}"
77
-
78
- subquery = self.select("MAX(id) as max_id").where(name: AhoyCaptain.config.event[:view_name]).route_in(*args).group(:visit_id)
79
- joins("INNER JOIN (#{subquery.to_sql}) #{table_alias} ON #{::AhoyCaptain.event.table_name}.id = #{table_alias}.max_id")
32
+ scope :distinct_url, -> {
33
+ distinct(Arel.sql("#{AhoyCaptain.config.event.url_column}"))
80
34
  }
81
35
 
82
- scope :exit_page_not_in, ->(*args) {
83
- table_alias = "last_events_#{SecureRandom.hex.first(6)}"
84
-
85
- subquery = self.select("MAX(id) as max_id").where(name: AhoyCaptain.config.event[:view_name]).route_not_in(*args).group(:visit_id)
86
- joins("INNER JOIN (#{subquery.to_sql}) #{table_alias} ON #{::AhoyCaptain.event.table_name}.id = #{table_alias}.max_id")
36
+ scope :property_name_eq, ->(value) {
37
+ where("properties ? :key", key: value)
87
38
  }
88
39
 
89
- scope :exit_page_i_cont, ->(arg) {
90
- table_alias = "last_events_#{SecureRandom.hex.first(6)}"
91
-
92
- subquery = self.select("MAX(id) as max_id").where(name: AhoyCaptain.config.event[:view_name]).route_i_cont(*arg).group(:visit_id)
93
- joins("INNER JOIN (#{subquery.to_sql}) #{table_alias} ON #{::AhoyCaptain.event.table_name}.id = #{table_alias}.max_id")
40
+ scope :properties_eq, ->(value) {
41
+ where("properties @> ?", value)
94
42
  }
95
43
 
44
+ scope :properties_not_eq, ->(value) do
45
+ where.not("properties::jsonb @> ?", value)
46
+ end
96
47
  end
97
48
 
98
49
  class_methods do
99
50
  def ransackable_attributes(auth_object = nil)
100
- super + ["action", "controller", "id", "id_property", "name", "name_property", "page", "properties", "time", "url", "user_id", "visit_id"] + self._ransackers.keys
51
+ super + ["action", "controller", "id", "id_property", "name", "page", "properties", "time", "url", "user_id", "visit_id", "property_name"] + self._ransackers.keys
101
52
  end
102
53
 
103
54
  def ransackable_scopes(auth_object = nil)
104
- super + [:entry_page_in, :entry_page_not_in, :exit_page_in, :entry_page_not_in, :route_in, :route_not_in, :route_i_cont, :entry_page_i_cont, :exit_page_i_cont]
55
+ super + [
56
+ :properties_eq,
57
+ :properties_not_eq
58
+ ]
105
59
  end
106
60
 
107
61
  def ransackable_associations(auth_object = nil)
@@ -13,7 +13,15 @@ module AhoyCaptain
13
13
  end
14
14
 
15
15
  def name(value)
16
- @event_query = -> { AhoyCaptain.event.where(name: value) }
16
+ @event_query = -> { ::AhoyCaptain.event.where(name: value) }
17
+ end
18
+
19
+ def event(value)
20
+ ActiveSupport::Deprecation.warn(
21
+ "event is deprecated. " \
22
+ "Use name instead."
23
+ )
24
+ name(value)
17
25
  end
18
26
 
19
27
  def query(&block)
@@ -1,3 +1,3 @@
1
1
  module AhoyCaptain
2
- VERSION = "0.81"
2
+ VERSION = "0.82"
3
3
  end