lookout-ahoy 0.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 (223) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +99 -0
  4. data/Rakefile +24 -0
  5. data/app/assets/images/lookout/apple-touch-icon.png +0 -0
  6. data/app/assets/images/lookout/favicon-16x16.png +0 -0
  7. data/app/assets/images/lookout/favicon-32x32.png +0 -0
  8. data/app/assets/images/lookout/logo.png +0 -0
  9. data/app/assets/images/lookout/safari-pinned-tab.png +0 -0
  10. data/app/assets/images/lookout/safari-pinned-tab.svg +199 -0
  11. data/app/assets/javascript/lookout/application.js +2 -0
  12. data/app/assets/javascript/lookout/controllers/application.js +9 -0
  13. data/app/assets/javascript/lookout/controllers/application_controller.js +33 -0
  14. data/app/assets/javascript/lookout/controllers/combobox_controller.js +371 -0
  15. data/app/assets/javascript/lookout/controllers/details_modal_controller.js +18 -0
  16. data/app/assets/javascript/lookout/controllers/dropdown_label_controller.js +39 -0
  17. data/app/assets/javascript/lookout/controllers/filter/item_controller.js +12 -0
  18. data/app/assets/javascript/lookout/controllers/filter_form_controller.js +13 -0
  19. data/app/assets/javascript/lookout/controllers/filter_modal_controller.js +45 -0
  20. data/app/assets/javascript/lookout/controllers/frame_link_controller.js +20 -0
  21. data/app/assets/javascript/lookout/controllers/funnel_chart_controller.js +159 -0
  22. data/app/assets/javascript/lookout/controllers/index.js +4 -0
  23. data/app/assets/javascript/lookout/controllers/interval_controller.js +15 -0
  24. data/app/assets/javascript/lookout/controllers/line_chart_controller.js +251 -0
  25. data/app/assets/javascript/lookout/controllers/predicate_select_controller.js +10 -0
  26. data/app/assets/javascript/lookout/controllers/properties_controller.js +8 -0
  27. data/app/assets/javascript/lookout/controllers/property_filter_controller.js +45 -0
  28. data/app/assets/javascript/lookout/controllers/realtime_controller.js +30 -0
  29. data/app/assets/javascript/lookout/controllers/sparkline_controller.js +64 -0
  30. data/app/assets/javascript/lookout/controllers/tile_controller.js +33 -0
  31. data/app/assets/javascript/lookout/controllers/toggle_controller.js +17 -0
  32. data/app/assets/javascript/lookout/helpers/chart_utils.js +156 -0
  33. data/app/assets/javascript/lookout/helpers/countries.js +2261 -0
  34. data/app/assets/javascript/lookout/helpers/number_formatters.js +55 -0
  35. data/app/assets/manifest/lookout/manifest.js +2 -0
  36. data/app/components/lookout/combobox_component.html.erb +33 -0
  37. data/app/components/lookout/combobox_component.rb +13 -0
  38. data/app/components/lookout/comparison_link_component.html.erb +17 -0
  39. data/app/components/lookout/comparison_link_component.rb +44 -0
  40. data/app/components/lookout/dropdown_button_component.html.erb +16 -0
  41. data/app/components/lookout/dropdown_button_component.rb +14 -0
  42. data/app/components/lookout/dropdown_link_component.html.erb +17 -0
  43. data/app/components/lookout/dropdown_link_component.rb +19 -0
  44. data/app/components/lookout/filter/dropdown_component.html.erb +50 -0
  45. data/app/components/lookout/filter/dropdown_component.rb +51 -0
  46. data/app/components/lookout/filter/modal_component.html.erb +16 -0
  47. data/app/components/lookout/filter/modal_component.rb +13 -0
  48. data/app/components/lookout/filter/select_component.html.erb +25 -0
  49. data/app/components/lookout/filter/select_component.rb +64 -0
  50. data/app/components/lookout/filter/tag_component.html.erb +13 -0
  51. data/app/components/lookout/filter/tag_component.rb +14 -0
  52. data/app/components/lookout/filter/tag_container_component.html.erb +4 -0
  53. data/app/components/lookout/filter/tag_container_component.rb +6 -0
  54. data/app/components/lookout/previous_next_component.html.erb +8 -0
  55. data/app/components/lookout/previous_next_component.rb +11 -0
  56. data/app/components/lookout/stats/comparable_container_component.html.erb +25 -0
  57. data/app/components/lookout/stats/comparable_container_component.rb +103 -0
  58. data/app/components/lookout/stats/container_component.html.erb +23 -0
  59. data/app/components/lookout/stats/container_component.rb +28 -0
  60. data/app/components/lookout/sticky_nav_component.html.erb +32 -0
  61. data/app/components/lookout/sticky_nav_component.rb +24 -0
  62. data/app/components/lookout/table_component.html.erb +16 -0
  63. data/app/components/lookout/table_component.rb +48 -0
  64. data/app/components/lookout/tables/devices_table_component.rb +11 -0
  65. data/app/components/lookout/tables/dynamic_table.rb +13 -0
  66. data/app/components/lookout/tables/dynamic_table_component.rb +207 -0
  67. data/app/components/lookout/tables/goals_table_component.rb +17 -0
  68. data/app/components/lookout/tables/header_component.html.erb +6 -0
  69. data/app/components/lookout/tables/header_component.rb +18 -0
  70. data/app/components/lookout/tables/headers/header_component.html.erb +5 -0
  71. data/app/components/lookout/tables/headers/header_component.rb +16 -0
  72. data/app/components/lookout/tables/properties_table_component.rb +27 -0
  73. data/app/components/lookout/tables/row_component.html.erb +4 -0
  74. data/app/components/lookout/tables/rows/row_component.html.erb +6 -0
  75. data/app/components/lookout/tables/rows/row_component.rb +40 -0
  76. data/app/components/lookout/tile_component.html.erb +24 -0
  77. data/app/components/lookout/tile_component.rb +24 -0
  78. data/app/components/lookout/tooltip_component.html.erb +3 -0
  79. data/app/components/lookout/tooltip_component.rb +18 -0
  80. data/app/controllers/lookout/application_controller.rb +83 -0
  81. data/app/controllers/lookout/campaigns_controller.rb +19 -0
  82. data/app/controllers/lookout/devices_controller.rb +20 -0
  83. data/app/controllers/lookout/entry_pages_controller.rb +19 -0
  84. data/app/controllers/lookout/exit_pages_controller.rb +19 -0
  85. data/app/controllers/lookout/exports_controller.rb +14 -0
  86. data/app/controllers/lookout/filters/base_controller.rb +15 -0
  87. data/app/controllers/lookout/filters/goals_controller.rb +9 -0
  88. data/app/controllers/lookout/filters/locations_controller.rb +11 -0
  89. data/app/controllers/lookout/filters/operating_systems/names_controller.rb +13 -0
  90. data/app/controllers/lookout/filters/operating_systems/versions_controller.rb +13 -0
  91. data/app/controllers/lookout/filters/pages/actions_controller.rb +13 -0
  92. data/app/controllers/lookout/filters/pages/entry_pages_controller.rb +14 -0
  93. data/app/controllers/lookout/filters/pages/exit_pages_controller.rb +15 -0
  94. data/app/controllers/lookout/filters/properties/names_controller.rb +29 -0
  95. data/app/controllers/lookout/filters/properties/values_controller.rb +15 -0
  96. data/app/controllers/lookout/filters/screens_controller.rb +11 -0
  97. data/app/controllers/lookout/filters/sources_controller.rb +11 -0
  98. data/app/controllers/lookout/filters/utms_controller.rb +10 -0
  99. data/app/controllers/lookout/funnels_controller.rb +8 -0
  100. data/app/controllers/lookout/goals_controller.rb +7 -0
  101. data/app/controllers/lookout/locations/cities_controller.rb +22 -0
  102. data/app/controllers/lookout/locations/countries_controller.rb +22 -0
  103. data/app/controllers/lookout/locations/maps_controller.rb +24 -0
  104. data/app/controllers/lookout/locations/regions_controller.rb +22 -0
  105. data/app/controllers/lookout/properties_controller.rb +73 -0
  106. data/app/controllers/lookout/realtimes_controller.rb +7 -0
  107. data/app/controllers/lookout/roots_controller.rb +6 -0
  108. data/app/controllers/lookout/sources_controller.rb +21 -0
  109. data/app/controllers/lookout/stats/base_controller.rb +148 -0
  110. data/app/controllers/lookout/stats/bounce_rates_controller.rb +12 -0
  111. data/app/controllers/lookout/stats/total_pageviews_controller.rb +10 -0
  112. data/app/controllers/lookout/stats/total_visits_controller.rb +10 -0
  113. data/app/controllers/lookout/stats/unique_visitors_controller.rb +11 -0
  114. data/app/controllers/lookout/stats/views_per_visits_controller.rb +11 -0
  115. data/app/controllers/lookout/stats/visit_durations_controller.rb +10 -0
  116. data/app/controllers/lookout/stats_controller.rb +7 -0
  117. data/app/controllers/lookout/top_pages_controller.rb +20 -0
  118. data/app/decorators/lookout/application_decorator.rb +58 -0
  119. data/app/decorators/lookout/campaign_decorator.rb +27 -0
  120. data/app/decorators/lookout/city_decorator.rb +24 -0
  121. data/app/decorators/lookout/country_decorator.rb +38 -0
  122. data/app/decorators/lookout/device_decorator.rb +27 -0
  123. data/app/decorators/lookout/entry_page_decorator.rb +7 -0
  124. data/app/decorators/lookout/exit_page_decorator.rb +7 -0
  125. data/app/decorators/lookout/page_decorator.rb +27 -0
  126. data/app/decorators/lookout/region_decorator.rb +28 -0
  127. data/app/decorators/lookout/source_decorator.rb +27 -0
  128. data/app/decorators/lookout/top_page_decorator.rb +7 -0
  129. data/app/helpers/lookout/application_helper.rb +124 -0
  130. data/app/models/concerns/lookout/compare_mode.rb +19 -0
  131. data/app/models/concerns/lookout/limitable.rb +17 -0
  132. data/app/models/concerns/lookout/range_options.rb +8 -0
  133. data/app/models/lookout/comparison_mode.rb +72 -0
  134. data/app/models/lookout/export.rb +48 -0
  135. data/app/models/lookout/filter_parser.rb +82 -0
  136. data/app/models/lookout/range_from_params.rb +78 -0
  137. data/app/models/lookout/rangeable.rb +7 -0
  138. data/app/models/lookout/widget.rb +15 -0
  139. data/app/presenters/lookout/dashboard_presenter.rb +53 -0
  140. data/app/presenters/lookout/funnel_presenter.rb +75 -0
  141. data/app/presenters/lookout/goals_presenter.rb +72 -0
  142. data/app/queries/concerns/lookout/comparable_queries.rb +25 -0
  143. data/app/queries/concerns/lookout/comparable_query.rb +152 -0
  144. data/app/queries/concerns/lookout/lazy_comparable_query.rb +42 -0
  145. data/app/queries/lookout/application_query.rb +186 -0
  146. data/app/queries/lookout/campaign_query.rb +14 -0
  147. data/app/queries/lookout/city_query.rb +14 -0
  148. data/app/queries/lookout/country_query.rb +10 -0
  149. data/app/queries/lookout/device_query.rb +10 -0
  150. data/app/queries/lookout/entry_pages_query.rb +18 -0
  151. data/app/queries/lookout/event_query.rb +42 -0
  152. data/app/queries/lookout/exit_pages_query.rb +19 -0
  153. data/app/queries/lookout/region_query.rb +14 -0
  154. data/app/queries/lookout/source_query.rb +11 -0
  155. data/app/queries/lookout/stats/average_views_per_visit_query.rb +20 -0
  156. data/app/queries/lookout/stats/average_visit_duration_query.rb +34 -0
  157. data/app/queries/lookout/stats/base_query.rb +18 -0
  158. data/app/queries/lookout/stats/bounce_rates_query.rb +33 -0
  159. data/app/queries/lookout/stats/total_pageviews_query.rb +9 -0
  160. data/app/queries/lookout/stats/total_visitors_query.rb +9 -0
  161. data/app/queries/lookout/stats/unique_visitors_query.rb +9 -0
  162. data/app/queries/lookout/stats/views_per_visit_query.rb +17 -0
  163. data/app/queries/lookout/stats/visit_duration_query.rb +19 -0
  164. data/app/queries/lookout/top_page_query.rb +13 -0
  165. data/app/queries/lookout/visit_query.rb +42 -0
  166. data/app/views/lookout/campaigns/index.html+details.erb +4 -0
  167. data/app/views/lookout/campaigns/index.html.erb +3 -0
  168. data/app/views/lookout/devices/_table.html.erb +2 -0
  169. data/app/views/lookout/devices/index.html+details.erb +4 -0
  170. data/app/views/lookout/devices/index.html.erb +3 -0
  171. data/app/views/lookout/entry_pages/index.html+details.erb +4 -0
  172. data/app/views/lookout/entry_pages/index.html.erb +3 -0
  173. data/app/views/lookout/exit_pages/index.html+details.erb +4 -0
  174. data/app/views/lookout/exit_pages/index.html.erb +3 -0
  175. data/app/views/lookout/funnels/index.html.erb +7 -0
  176. data/app/views/lookout/funnels/show.html.erb +15 -0
  177. data/app/views/lookout/goals/index.html.erb +4 -0
  178. data/app/views/lookout/layouts/application.html.erb +144 -0
  179. data/app/views/lookout/layouts/shared/_tile_loader.html.erb +5 -0
  180. data/app/views/lookout/layouts/shared/_widget_disabled.html+details.erb +3 -0
  181. data/app/views/lookout/layouts/shared/_widget_disabled.html.erb +3 -0
  182. data/app/views/lookout/locations/cities/index.html+details.erb +4 -0
  183. data/app/views/lookout/locations/cities/index.html.erb +3 -0
  184. data/app/views/lookout/locations/countries/index.html+details.erb +5 -0
  185. data/app/views/lookout/locations/countries/index.html.erb +3 -0
  186. data/app/views/lookout/locations/maps/_simple_map.html.erb +26 -0
  187. data/app/views/lookout/locations/maps/show.html.erb +106 -0
  188. data/app/views/lookout/locations/regions/index.html+details.erb +4 -0
  189. data/app/views/lookout/locations/regions/index.html.erb +3 -0
  190. data/app/views/lookout/properties/_form.html.erb +6 -0
  191. data/app/views/lookout/properties/index.html.erb +3 -0
  192. data/app/views/lookout/properties/show.html.erb +6 -0
  193. data/app/views/lookout/realtimes/show.html.erb +9 -0
  194. data/app/views/lookout/roots/_filters.html.erb +80 -0
  195. data/app/views/lookout/roots/show.html.erb +191 -0
  196. data/app/views/lookout/sources/index.html+details.erb +4 -0
  197. data/app/views/lookout/sources/index.html.erb +3 -0
  198. data/app/views/lookout/stats/base/index.html.erb +40 -0
  199. data/app/views/lookout/stats/show.html.erb +15 -0
  200. data/app/views/lookout/top_pages/index.html+details.erb +4 -0
  201. data/app/views/lookout/top_pages/index.html.erb +3 -0
  202. data/config/routes.rb +69 -0
  203. data/lib/generators/lookout/install_generator.rb +31 -0
  204. data/lib/generators/lookout/migration_generator.rb +21 -0
  205. data/lib/generators/lookout/templates/config.rb.tt +185 -0
  206. data/lib/generators/lookout/templates/migration.rb.tt +7 -0
  207. data/lib/lookout/active_record.rb +108 -0
  208. data/lib/lookout/ahoy/event_methods.rb +75 -0
  209. data/lib/lookout/ahoy/visit_methods.rb +24 -0
  210. data/lib/lookout/configuration.rb +58 -0
  211. data/lib/lookout/database_adapter.rb +168 -0
  212. data/lib/lookout/engine.rb +47 -0
  213. data/lib/lookout/filter_configuration/filter.rb +16 -0
  214. data/lib/lookout/filter_configuration/filter_collection.rb +48 -0
  215. data/lib/lookout/filters_configuration.rb +77 -0
  216. data/lib/lookout/funnels.rb +44 -0
  217. data/lib/lookout/goals.rb +51 -0
  218. data/lib/lookout/period_collection.rb +115 -0
  219. data/lib/lookout/predicate_label.rb +7 -0
  220. data/lib/lookout/railtie.rb +9 -0
  221. data/lib/lookout/version.rb +3 -0
  222. data/lib/lookout.rb +78 -0
  223. metadata +673 -0
@@ -0,0 +1,83 @@
1
+ module Lookout
2
+ class ApplicationController < ActionController::Base
3
+ include Pagy::Backend
4
+ include CompareMode
5
+ include RangeOptions
6
+ include Rangeable
7
+
8
+ layout 'lookout/layouts/application'
9
+
10
+ def period
11
+ params[:period] || Lookout.config.ranges.default
12
+ end
13
+
14
+ # show the details frame
15
+ before_action :use_details_frame
16
+
17
+ # act like an spa without being an spa
18
+ before_action :act_like_an_spa
19
+
20
+ rescue_from Widget::WidgetDisabled do |e|
21
+ respond_to do |f|
22
+ f.turbo_stream do
23
+ render(partial: "lookout/layouts/shared/widget_disabled", locals: { frame: e.frame })
24
+ end
25
+ f.html do
26
+ render(partial: "lookout/layouts/shared/widget_disabled", locals: { frame: e.frame })
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def use_details_frame
34
+ if request.headers['Turbo-Frame'] == 'details'
35
+ request.variant = :details
36
+ end
37
+ end
38
+
39
+ def act_like_an_spa
40
+ if request.format.html? && request.headers['Turbo-Frame'].blank?
41
+ if request.path != root_path
42
+ requested_params = Rails.application.routes.recognize_path(request.path).except(:controller, :action)
43
+ params.merge!(requested_params)
44
+ unless params[:debug]
45
+ render template: 'lookout/roots/show'
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def visit_query
52
+ VisitQuery.call(params)
53
+ end
54
+
55
+ def event_query
56
+ EventQuery.call(params)
57
+ end
58
+
59
+ # Only paginate details requests requests
60
+ def paginate(collection)
61
+ if paginate?
62
+ pagy, results = pagy(collection, page: params[:page])
63
+ @pagination = pagy
64
+ return results
65
+ end
66
+
67
+ collection
68
+ end
69
+
70
+ def paginate?
71
+ request.variant.include?(:details)
72
+ end
73
+
74
+ def cached(*names)
75
+ if Lookout.cache.class == ActiveSupport::Cache::NullStore
76
+ return yield
77
+ end
78
+ Lookout.cache.fetch("lookout:#{names.join(":")}:#{request.query_parameters.sort.map { |k,v| "#{k}-#{v}" }.join(":")}", expire_in: Lookout.config.cache[:ttl]) do
79
+ yield
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,19 @@
1
+ module Lookout
2
+ class CampaignsController < ApplicationController
3
+ include Limitable
4
+
5
+ before_action do
6
+ if Widget.disabled?(:campaigns, params[:campaigns_type])
7
+ raise Widget::WidgetDisabled.new("Widget disabled", :sources)
8
+ end
9
+ end
10
+
11
+ def index
12
+ results = cached(:campaigns, params[:campaigns_type]) do
13
+ CampaignQuery.call(params).limit(limit)
14
+ end
15
+ @campaigns = paginate(results).map { |campaign| CampaignDecorator.new(campaign, self) }
16
+ @campaign_type = params[:campaigns_type]&.titleize&.gsub("Utm", "UTM")
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Lookout
2
+ class DevicesController < ApplicationController
3
+ include Limitable
4
+
5
+ before_action do
6
+ if Widget.disabled?(:devices, params[:devices_type])
7
+ raise Widget::WidgetDisabled.new("Widget disabled", :devices)
8
+ end
9
+ end
10
+
11
+ def index
12
+ results = cached(:devices, params[:devices_type]) do
13
+ DeviceQuery.call(params)
14
+ .limit(limit)
15
+ end
16
+
17
+ @devices = results.map { |device| DeviceDecorator.new(device, self) }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module Lookout
2
+ class EntryPagesController < ApplicationController
3
+ include Limitable
4
+
5
+ before_action do
6
+ if Widget.disabled?(:entry_pages)
7
+ raise Widget::WidgetDisabled.new("Widget disabled", :pages)
8
+ end
9
+ end
10
+
11
+ def index
12
+ results = cached(:entry_pages) do
13
+ EntryPagesQuery.call(params).limit(limit)
14
+ end
15
+
16
+ @pages = paginate(results).map { |page| EntryPageDecorator.new(page, self) }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Lookout
2
+ class ExitPagesController < ApplicationController
3
+ include Limitable
4
+
5
+ before_action do
6
+ if Widget.disabled?(:exit_pages)
7
+ raise Widget::WidgetDisabled.new("Widget disabled", :pages)
8
+ end
9
+ end
10
+
11
+ def index
12
+ results = cached(:exit_pages) do
13
+ ExitPagesQuery.call(params)
14
+ .limit(limit)
15
+ end
16
+ @pages = paginate(results).map { |page| ExitPageDecorator.new(page, self) }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Lookout
2
+ class ExportsController < ApplicationController
3
+ skip_before_action :act_like_an_spa
4
+
5
+ def show
6
+ export = Export.new(params, self).build
7
+ file = export.to_zip
8
+ send_data file.read,
9
+ type: 'application/zip',
10
+ disposition: 'attachment',
11
+ filename: "Lookout export #{request.host} #{range[0].to_date} to #{(range[1] || Time.current).to_date}.zip"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Lookout
2
+ module Filters
3
+ class BaseController < ApplicationController
4
+ private
5
+
6
+ def serialize(value)
7
+ { text: (value.presence || Lookout.none.text), value: (value.presence || Lookout.none.value) }
8
+ end
9
+
10
+ def visit_query
11
+ VisitQuery.call(params)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Lookout
2
+ module Filters
3
+ class GoalsController < BaseController
4
+ def index
5
+ render json: Lookout.configuration.goals.map { |goal| { text: goal.title, value: goal.id } }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module Lookout
2
+ module Filters
3
+ class LocationsController < BaseController
4
+ def index
5
+ query = visit_query.all
6
+
7
+ render json: query.select("distinct #{params[:type]}").where.not(params[:type] => nil).group(params[:type]).order(Arel.sql "count(*) desc").limit(50).pluck(params[:type]).map { |city| serialize(city) }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Lookout
2
+ module Filters
3
+ module OperatingSystems
4
+ class NamesController < BaseController
5
+ def index
6
+ query = visit_query.all
7
+
8
+ render json: query.select("distinct os").where.not(os: nil).group(:os).order(Arel.sql "count(*) desc").pluck(:os).map { |city| serialize(city) }
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Lookout
2
+ module Filters
3
+ module OperatingSystems
4
+ class VersionsController < BaseController
5
+ def index
6
+ query = visit_query.all
7
+
8
+ render json: query.select("distinct os_version").where.not(os_version: nil).group(:os_version).order(Arel.sql "count(*) desc").pluck(:os_version).map { |city| serialize(city) }
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Lookout
2
+ module Filters
3
+ module Pages
4
+ class ActionsController < BaseController
5
+ def index
6
+ query = event_query.all.with_url.distinct_url
7
+
8
+ render json: query.map { |row| serialize(row.url) }
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Lookout
2
+ module Filters
3
+ module Pages
4
+ class EntryPagesController < BaseController
5
+ def index
6
+ query = event_query.all.distinct("entry_pages.url").select("entry_pages.url as url")
7
+
8
+ render json: query.map { |row| serialize(row.url) }
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Lookout
2
+ module Filters
3
+ module Pages
4
+ class ExitPagesController < BaseController
5
+ def index
6
+ query = event_query.distinct("exit_pages.url").select("exit_pages.url as url")
7
+
8
+ render json: query.map { |row| serialize(row.url) }
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,29 @@
1
+ module Lookout
2
+ module Filters
3
+ module Properties
4
+ class NamesController < BaseController
5
+ def index
6
+ keys = extract_property_keys
7
+ render json: keys.map { |key| serialize(key) }
8
+ end
9
+
10
+ private
11
+
12
+ def extract_property_keys
13
+ if Lookout::DatabaseAdapter.postgresql?
14
+ ::Ahoy::Event
15
+ .select("jsonb_object_keys(properties) as keys")
16
+ .distinct
17
+ .pluck("jsonb_object_keys(properties)")
18
+ else
19
+ # SQLite: use json_each to extract keys
20
+ ::Ahoy::Event
21
+ .from("#{::Ahoy::Event.table_name}, json_each(#{::Ahoy::Event.table_name}.properties)")
22
+ .select("DISTINCT json_each.key as keys")
23
+ .pluck("json_each.key")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ module Lookout
2
+ module Filters
3
+ module Properties
4
+ class ValuesController < BaseController
5
+ def index
6
+ param_key = params[:q].to_unsafe_h.detect { |k,v| k.ends_with?("_i_cont") && k.starts_with?("properties.") }[0]
7
+ key = param_key.delete_prefix("properties.").delete_suffix("_i_cont")
8
+ query = event_query.all.distinct.select("properties->>'#{key}'").pluck(Arel.sql "properties->>'#{key}'")
9
+
10
+ render json: query.map { |element| serialize(element) }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Lookout
2
+ module Filters
3
+ class ScreensController < BaseController
4
+ def index
5
+ query = visit_query.all
6
+
7
+ render json: query.select("distinct device_type").where.not(device_type: nil).group(:device_type).order(Arel.sql "count(*) desc").pluck(:device_type).map { |city| serialize(city) }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Lookout
2
+ module Filters
3
+ class SourcesController < BaseController
4
+ def index
5
+ query = visit_query.all
6
+
7
+ render json: query.select("distinct referring_domain").where.not(referring_domain: nil).group(:referring_domain).order(Arel.sql "count(*) desc").pluck(:referring_domain).map { |city| serialize(city) }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Lookout
2
+ module Filters
3
+ class UtmsController < BaseController
4
+ def index
5
+ query = visit_query.select("#{params[:type]}", "count(#{params[:type]}) as total").group(params[:type]).order(Arel.sql "count(#{params[:type]}) desc").pluck(params[:type]).map { |city| serialize(city) }
6
+ render json: query
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module Lookout
2
+ class FunnelsController < ApplicationController
3
+ def show
4
+ funnel = Lookout.configuration.funnels[params[:id]]
5
+ @funnel = FunnelPresenter.new(funnel, event_query).build
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Lookout
2
+ class GoalsController < ApplicationController
3
+ def index
4
+ @presenter = GoalsPresenter.new(event_query).build
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module Lookout
2
+ module Locations
3
+ class CitiesController < ApplicationController
4
+ include Lookout::Limitable
5
+
6
+ before_action do
7
+ if Widget.disabled?(:locations, :cities)
8
+ raise Widget::WidgetDisabled.new("Widget disabled", :geography)
9
+ end
10
+ end
11
+
12
+ def index
13
+ results = cached(:cities) do
14
+ CityQuery.call(params)
15
+ .limit(limit)
16
+ end
17
+
18
+ @cities = paginate(results).map { |city| CityDecorator.new(city, self) }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Lookout
2
+ module Locations
3
+ class CountriesController < ApplicationController
4
+ include Limitable
5
+
6
+ before_action do
7
+ if Widget.disabled?(:locations, :countries)
8
+ raise Widget::WidgetDisabled.new("Widget disabled", :geography)
9
+ end
10
+ end
11
+
12
+ def index
13
+ results = cached(:countries) do
14
+ CountryQuery.call(params)
15
+ .limit(limit)
16
+ end
17
+
18
+ @countries = paginate(results).map { |country| CountryDecorator.new(country, self) }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module Lookout
2
+ module Locations
3
+ class MapsController < ApplicationController
4
+ include Limitable
5
+
6
+ before_action do
7
+ if Widget.disabled?(:locations, :map)
8
+ raise Widget::WidgetDisabled.new("Widget disabled", :geography)
9
+ end
10
+ end
11
+
12
+ def show
13
+ if request.variant.include?(:details)
14
+ results = CountryQuery.call(params)
15
+ results = results.limit(limit)
16
+ @countries = paginate(results).map { |country| CountryDecorator.new(country, self) }
17
+ render template: 'lookout/locations/countries/index'
18
+ else
19
+ @countries = visit_query.where.not(country: nil).group("country").count
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module Lookout
2
+ module Locations
3
+ class RegionsController < ApplicationController
4
+ include Limitable
5
+
6
+ before_action do
7
+ if Widget.disabled?(:locations, :regions)
8
+ raise Widget::WidgetDisabled.new("Widget disabled", :geography)
9
+ end
10
+ end
11
+
12
+ def index
13
+ results = cached(:regions) do
14
+ RegionQuery.call(params)
15
+ .limit(limit)
16
+ end
17
+
18
+ @regions = paginate(results).map { |region| RegionDecorator.new(region, self) }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,73 @@
1
+ module Lookout
2
+ class PropertiesController < ApplicationController
3
+ before_action do
4
+ @options = extract_property_keys.map { |key| [Base64.urlsafe_encode64(key), key]}.to_h
5
+ end
6
+
7
+ def index
8
+ # Auto-select 'url' property if it exists, otherwise first property
9
+ if @options.any?
10
+ # Prefer 'url' property
11
+ url_key = @options.find { |key, value| value == 'url' }&.first
12
+ default_key = url_key || @options.keys.first
13
+
14
+ redirect_to property_path(id: default_key, **request.query_parameters), status: :see_other
15
+ end
16
+ end
17
+
18
+ def show
19
+ value = Base64.urlsafe_decode64(params[:id])
20
+ json_extract = Lookout::DatabaseAdapter.json_extract_text("properties", value)
21
+ coalesce_expr = "COALESCE(#{json_extract}, '(none)')"
22
+
23
+ # Handle percentage calculation differently for SQLite vs PostgreSQL
24
+ percentage_calc = if Lookout::DatabaseAdapter.postgresql?
25
+ "(COUNT(DISTINCT visit_id)/COUNT(*)::numeric) * 100"
26
+ else
27
+ "(CAST(COUNT(DISTINCT visit_id) AS REAL) / COUNT(*)) * 100"
28
+ end
29
+
30
+ @properties = event_query
31
+ .select(
32
+ "#{coalesce_expr} AS label",
33
+ "COUNT(*) AS events_count",
34
+ "COUNT(DISTINCT visit_id) AS unique_visitors_count",
35
+ "#{percentage_calc} as percentage"
36
+ )
37
+ .group(coalesce_expr)
38
+ .order(Arel.sql "COUNT(*) desc")
39
+ end
40
+
41
+ private
42
+
43
+ helper_method :has_property?
44
+ def has_property?(value)
45
+ searching_properties[value]
46
+ end
47
+
48
+ helper_method :selected_property?
49
+ def selected_property?(value)
50
+ encoded = Base64.urlsafe_encode64(value, padding: false)
51
+ encoded == params[:id]
52
+ end
53
+
54
+ def searching_properties
55
+ JSON.parse(params.dig("q", "properties_json_cont") || '{}')
56
+ end
57
+
58
+ def extract_property_keys
59
+ if Lookout::DatabaseAdapter.postgresql?
60
+ ::Ahoy::Event
61
+ .select("jsonb_object_keys(properties) as keys")
62
+ .distinct
63
+ .pluck("jsonb_object_keys(properties)")
64
+ else
65
+ # SQLite: use json_each to extract keys
66
+ ::Ahoy::Event
67
+ .from("#{::Ahoy::Event.table_name}, json_each(#{::Ahoy::Event.table_name}.properties)")
68
+ .select("DISTINCT json_each.key as keys")
69
+ .pluck("json_each.key")
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,7 @@
1
+ module Lookout
2
+ class RealtimesController < ApplicationController
3
+ def show
4
+ @total = event_query.where("#{Lookout.event.table_name}.time > ?", 1.minute.ago).distinct(:visit_id).count(:visit_id)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Lookout
2
+ class RootsController < ApplicationController
3
+ def show
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module Lookout
2
+ class SourcesController < ApplicationController
3
+ include Limitable
4
+
5
+ before_action do
6
+ if Widget.disabled?(:sources)
7
+ raise Widget::WidgetDisabled.new("Widget disabled", :sources)
8
+ end
9
+ end
10
+
11
+ def index
12
+ results = cached(:sources) do
13
+ SourceQuery.call(params)
14
+ .limit(limit)
15
+ end
16
+
17
+ @sources = paginate(results).map { |source| Lookout::SourceDecorator.new(source, self) }
18
+ end
19
+ end
20
+
21
+ end