ahoy_captain 0.1.0 → 0.76

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +55 -14
  4. data/Rakefile +1 -10
  5. data/app/assets/images/ahoy_captain/apple-touch-icon.png +0 -0
  6. data/app/assets/images/ahoy_captain/favicon-16x16.png +0 -0
  7. data/app/assets/images/ahoy_captain/favicon-32x32.png +0 -0
  8. data/app/assets/images/ahoy_captain/logo.png +0 -0
  9. data/app/assets/images/ahoy_captain/safari-pinned-tab.svg +199 -0
  10. data/app/assets/javascript/ahoy_captain/application.js +4 -0
  11. data/app/assets/javascript/ahoy_captain/controllers/application.js +9 -0
  12. data/app/assets/javascript/ahoy_captain/controllers/application_controller.js +14 -0
  13. data/app/assets/javascript/ahoy_captain/controllers/details_modal_controller.js +18 -0
  14. data/app/assets/javascript/ahoy_captain/controllers/dropdown_label_controller.js +14 -0
  15. data/app/assets/javascript/ahoy_captain/controllers/filter_controller.js +145 -0
  16. data/app/assets/javascript/ahoy_captain/controllers/filter_tag_controller.js +17 -0
  17. data/app/assets/javascript/ahoy_captain/controllers/funnel_chart_controller.js +143 -0
  18. data/app/assets/javascript/ahoy_captain/controllers/index.js +3 -0
  19. data/app/assets/javascript/ahoy_captain/controllers/link_controller.js +43 -0
  20. data/app/assets/javascript/ahoy_captain/controllers/navigation_controller.js +25 -0
  21. data/app/assets/javascript/ahoy_captain/controllers/realtime_controller.js +27 -0
  22. data/app/assets/manifest/ahoy_captain/manifest.js +2 -0
  23. data/app/components/ahoy_captain/dropdown_button_component.html.erb +16 -0
  24. data/app/components/ahoy_captain/dropdown_button_component.rb +14 -0
  25. data/app/components/ahoy_captain/dropdown_link_component.html.erb +19 -0
  26. data/app/components/ahoy_captain/dropdown_link_component.rb +15 -0
  27. data/app/components/ahoy_captain/filter/modal_component.html.erb +13 -0
  28. data/app/components/ahoy_captain/filter/modal_component.rb +13 -0
  29. data/app/components/ahoy_captain/filter/select_component.html.erb +21 -0
  30. data/app/components/ahoy_captain/filter/select_component.rb +32 -0
  31. data/app/components/ahoy_captain/filter/tag_component.html.erb +9 -0
  32. data/app/components/ahoy_captain/filter/tag_component.rb +38 -0
  33. data/app/components/ahoy_captain/filter/tag_container_component.html.erb +5 -0
  34. data/app/components/ahoy_captain/filter/tag_container_component.rb +13 -0
  35. data/app/components/ahoy_captain/sticky_nav_component.html.erb +37 -0
  36. data/app/components/ahoy_captain/sticky_nav_component.rb +5 -0
  37. data/app/components/ahoy_captain/table_component.html.erb +49 -0
  38. data/app/components/ahoy_captain/table_component.rb +28 -0
  39. data/app/components/ahoy_captain/tile_component.html.erb +12 -0
  40. data/app/components/ahoy_captain/tile_component.rb +16 -0
  41. data/app/components/ahoy_captain/tooltip_component.html.erb +3 -0
  42. data/app/components/ahoy_captain/tooltip_component.rb +18 -0
  43. data/app/controllers/ahoy_captain/application_controller.rb +83 -0
  44. data/app/controllers/ahoy_captain/campaigns_controller.rb +27 -0
  45. data/app/controllers/ahoy_captain/cities_controller.rb +24 -0
  46. data/app/controllers/ahoy_captain/countries_controller.rb +24 -0
  47. data/app/controllers/ahoy_captain/devices_controller.rb +23 -0
  48. data/app/controllers/ahoy_captain/entry_pages_controller.rb +21 -0
  49. data/app/controllers/ahoy_captain/exit_pages_controller.rb +20 -0
  50. data/app/controllers/ahoy_captain/filters/base_controller.rb +17 -0
  51. data/app/controllers/ahoy_captain/filters/locations_controller.rb +11 -0
  52. data/app/controllers/ahoy_captain/filters/operating_systems/names_controller.rb +13 -0
  53. data/app/controllers/ahoy_captain/filters/operating_systems/versions_controller.rb +13 -0
  54. data/app/controllers/ahoy_captain/filters/pages/actions_controller.rb +13 -0
  55. data/app/controllers/ahoy_captain/filters/pages/entry_pages_controller.rb +14 -0
  56. data/app/controllers/ahoy_captain/filters/pages/exit_pages_controller.rb +16 -0
  57. data/app/controllers/ahoy_captain/filters/screens_controller.rb +11 -0
  58. data/app/controllers/ahoy_captain/filters/sources_controller.rb +11 -0
  59. data/app/controllers/ahoy_captain/filters/utms_controller.rb +10 -0
  60. data/app/controllers/ahoy_captain/funnels_controller.rb +8 -0
  61. data/app/controllers/ahoy_captain/goals_controller.rb +7 -0
  62. data/app/controllers/ahoy_captain/realtimes_controller.rb +7 -0
  63. data/app/controllers/ahoy_captain/regions_controller.rb +24 -0
  64. data/app/controllers/ahoy_captain/roots_controller.rb +6 -0
  65. data/app/controllers/ahoy_captain/sources_controller.rb +24 -0
  66. data/app/controllers/ahoy_captain/stats/base_controller.rb +6 -0
  67. data/app/controllers/ahoy_captain/stats/bounce_rates_controller.rb +10 -0
  68. data/app/controllers/ahoy_captain/stats/total_pageviews_controller.rb +9 -0
  69. data/app/controllers/ahoy_captain/stats/total_visits_controller.rb +9 -0
  70. data/app/controllers/ahoy_captain/stats/unique_visitors_controller.rb +9 -0
  71. data/app/controllers/ahoy_captain/stats/views_per_visits_controller.rb +16 -0
  72. data/app/controllers/ahoy_captain/stats/visit_durations_controller.rb +9 -0
  73. data/app/controllers/ahoy_captain/stats_controller.rb +7 -0
  74. data/app/controllers/ahoy_captain/top_pages_controller.rb +26 -0
  75. data/app/decorators/ahoy_captain/application_decorator.rb +34 -0
  76. data/app/decorators/ahoy_captain/campaign_decorator.rb +19 -0
  77. data/app/decorators/ahoy_captain/city_decorator.rb +12 -0
  78. data/app/decorators/ahoy_captain/country_decorator.rb +28 -0
  79. data/app/decorators/ahoy_captain/device_decorator.rb +16 -0
  80. data/app/decorators/ahoy_captain/entry_page_decorator.rb +7 -0
  81. data/app/decorators/ahoy_captain/exit_page_decorator.rb +7 -0
  82. data/app/decorators/ahoy_captain/page_decorator.rb +16 -0
  83. data/app/decorators/ahoy_captain/region_decorator.rb +12 -0
  84. data/app/decorators/ahoy_captain/source_decorator.rb +20 -0
  85. data/app/decorators/ahoy_captain/top_page_decorator.rb +7 -0
  86. data/app/helpers/ahoy_captain/application_helper.rb +32 -0
  87. data/app/models/ahoy_captain/current.rb +9 -0
  88. data/app/models/ahoy_captain/rangeable.rb +10 -0
  89. data/app/models/ahoy_captain/url_helpers.rb +6 -0
  90. data/app/models/ahoy_captain/widget.rb +15 -0
  91. data/app/models/concerns/ahoy_captain/range_options.rb +21 -0
  92. data/app/presenters/ahoy_captain/dashboard_presenter.rb +81 -0
  93. data/app/presenters/ahoy_captain/funnel_presenter.rb +65 -0
  94. data/app/presenters/ahoy_captain/goals_presenter.rb +60 -0
  95. data/app/queries/ahoy_captain/application_query.rb +118 -0
  96. data/app/queries/ahoy_captain/entry_pages_query.rb +17 -0
  97. data/app/queries/ahoy_captain/event_query.rb +20 -0
  98. data/app/queries/ahoy_captain/exit_pages_query.rb +17 -0
  99. data/app/queries/ahoy_captain/stats/average_views_per_visit_query.rb +13 -0
  100. data/app/queries/ahoy_captain/stats/average_visit_duration_query.rb +15 -0
  101. data/app/queries/ahoy_captain/stats/bounce_rates_query.rb +14 -0
  102. data/app/queries/ahoy_captain/stats/total_pageviews_query.rb +9 -0
  103. data/app/queries/ahoy_captain/stats/total_visitors_query.rb +9 -0
  104. data/app/queries/ahoy_captain/stats/unique_visitors_query.rb +9 -0
  105. data/app/queries/ahoy_captain/stats/views_per_visit_query.rb +17 -0
  106. data/app/queries/ahoy_captain/stats/visit_duration_query.rb +16 -0
  107. data/app/queries/ahoy_captain/visit_query.rb +32 -0
  108. data/app/views/ahoy_captain/campaigns/index.html+details.erb +4 -0
  109. data/app/views/ahoy_captain/campaigns/index.html.erb +3 -0
  110. data/app/views/ahoy_captain/cities/index.html+details.erb +4 -0
  111. data/app/views/ahoy_captain/cities/index.html.erb +3 -0
  112. data/app/views/ahoy_captain/countries/index.html+details.erb +5 -0
  113. data/app/views/ahoy_captain/countries/index.html.erb +3 -0
  114. data/app/views/ahoy_captain/devices/index.html+details.erb +4 -0
  115. data/app/views/ahoy_captain/devices/index.html.erb +3 -0
  116. data/app/views/ahoy_captain/entry_pages/index.html+details.erb +4 -0
  117. data/app/views/ahoy_captain/entry_pages/index.html.erb +3 -0
  118. data/app/views/ahoy_captain/exit_pages/index.html+details.erb +4 -0
  119. data/app/views/ahoy_captain/exit_pages/index.html.erb +3 -0
  120. data/app/views/ahoy_captain/funnels/index.html.erb +7 -0
  121. data/app/views/ahoy_captain/funnels/show.html.erb +6 -0
  122. data/app/views/ahoy_captain/goals/index.html.erb +39 -0
  123. data/app/views/ahoy_captain/layouts/application.html.erb +127 -0
  124. data/app/views/ahoy_captain/realtimes/show.html.erb +9 -0
  125. data/app/views/ahoy_captain/regions/index.html+details.erb +4 -0
  126. data/app/views/ahoy_captain/regions/index.html.erb +3 -0
  127. data/app/views/ahoy_captain/roots/show.html.erb +179 -0
  128. data/app/views/ahoy_captain/sources/index.html+details.erb +4 -0
  129. data/app/views/ahoy_captain/sources/index.html.erb +3 -0
  130. data/app/views/ahoy_captain/stats/base/index.html.erb +3 -0
  131. data/app/views/ahoy_captain/stats/show.html.erb +57 -0
  132. data/app/views/ahoy_captain/top_pages/index.html+details.erb +4 -0
  133. data/app/views/ahoy_captain/top_pages/index.html.erb +3 -0
  134. data/config/routes.rb +56 -0
  135. data/lib/ahoy_captain/active_record.rb +108 -0
  136. data/lib/ahoy_captain/ahoy/event_methods.rb +114 -0
  137. data/lib/ahoy_captain/ahoy/visit_methods.rb +23 -0
  138. data/lib/ahoy_captain/configuration.rb +43 -0
  139. data/lib/ahoy_captain/engine.rb +24 -0
  140. data/lib/ahoy_captain/funnels.rb +44 -0
  141. data/lib/ahoy_captain/goals.rb +39 -0
  142. data/lib/ahoy_captain/period_collection.rb +115 -0
  143. data/lib/ahoy_captain/railtie.rb +7 -0
  144. data/lib/ahoy_captain/version.rb +1 -3
  145. data/lib/ahoy_captain.rb +50 -4
  146. data/lib/generators/ahoy_captain/install_generator.rb +18 -0
  147. data/lib/generators/ahoy_captain/templates/config.rb.tt +123 -0
  148. metadata +393 -17
  149. data/.rspec +0 -3
  150. data/.rubocop.yml +0 -13
  151. data/CHANGELOG.md +0 -5
  152. data/Gemfile +0 -12
  153. data/ahoy_captain.gemspec +0 -37
  154. data/sig/ahoy_captain.rbs +0 -4
data/config/routes.rb ADDED
@@ -0,0 +1,56 @@
1
+ AhoyCaptain::Engine.routes.draw do
2
+ root to: 'roots#show'
3
+ %w{utm_source utm_medium utm_term utm_content utm_campaign}.each do |utm|
4
+ get "campaigns/#{utm}" => "campaigns#index", defaults: { campaigns_type: utm }, as: "campaign_#{utm}"
5
+ end
6
+
7
+ {
8
+ browsers: :browser,
9
+ operating_systems: :os,
10
+ device_types: :device_type
11
+ }.each do |k,v|
12
+ get "/devices/#{k}" => 'devices#index', defaults: { devices_type: v }, as: "devices_#{k}"
13
+ end
14
+
15
+ resource :realtime, only: [:show]
16
+ resources :funnels, only: [:show]
17
+ resources :goals, only: [:index]
18
+ resource :stats, only: [:show]
19
+ resources :countries, only: [:index]
20
+ resources :regions, only: [:index]
21
+ resources :cities, only: [:index]
22
+ resources :campaigns, only: [:index]
23
+ resources :sources, only: [:index]
24
+ resources :exit_pages, only: [:index]
25
+ resources :top_pages, only: [:index]
26
+ resources :entry_pages, only: [:index]
27
+
28
+ namespace :stats do
29
+ resources :unique_visitors, only: [:index]
30
+ resources :total_visits, only: [:index]
31
+ resources :total_pageviews, only: [:index]
32
+ resources :views_per_visits, only: [:index]
33
+ resources :bounce_rates, only: [:index]
34
+ resources :visit_durations, only: [:index]
35
+ end
36
+ namespace :filters do
37
+ %w{source medium term content campaign}.each do |utm|
38
+ get "utm/#{utm}s" => "utms#index", defaults: { type: "utm_#{utm}" }
39
+ end
40
+
41
+ %w{country region city}.each do |type|
42
+ get "locations/#{type.pluralize}" => "locations#index", defaults: { type: type }
43
+ end
44
+ resources :sources, only: [:index]
45
+ resources :screens, only: [:index]
46
+ scope :operating_systems, module: :operating_systems do
47
+ resources :names, only: [:index]
48
+ resources :versions, only: [:index]
49
+ end
50
+ scope :pages, module: :pages do
51
+ resources :actions, only: [:index]
52
+ resources :entry_pages, only: [:index]
53
+ resources :exit_pages, only: [:index]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,108 @@
1
+ module ActiveRecord
2
+ module Querying
3
+ delegate :with, to: :all
4
+ end
5
+
6
+ module WithMerger
7
+ def merge
8
+ super
9
+ merge_withs
10
+ relation
11
+ end
12
+
13
+ private
14
+
15
+ def merge_withs
16
+ relation.recursive_with = true if other.recursive_with?
17
+ other_values = other.with_values.reject { |value| relation.with_values.include?(value) }
18
+ relation.with!(*other_values) if other_values.any?
19
+ end
20
+ end
21
+
22
+ class Relation
23
+ class Merger
24
+ prepend WithMerger
25
+ end
26
+
27
+ def with(opts, *rest)
28
+ spawn.with!(opts, *rest)
29
+ end
30
+
31
+ def with!(opts, *rest)
32
+ if opts == :recursive
33
+ self.recursive_with = true
34
+ self.with_values += rest
35
+ else
36
+ self.with_values += [opts] + rest
37
+ end
38
+ self
39
+ end
40
+
41
+ def with_values
42
+ @values[:with] || []
43
+ end
44
+
45
+ def with_values=(values)
46
+ raise ImmutableRelation if @loaded
47
+
48
+ @values[:with] = values
49
+ end
50
+
51
+ def recursive_with?
52
+ @values[:recursive_with]
53
+ end
54
+
55
+ def recursive_with=(value)
56
+ raise ImmutableRelation if @loaded
57
+
58
+ @values[:recursive_with] = value
59
+ end
60
+
61
+ private
62
+
63
+ def build_arel(*args)
64
+ arel = super
65
+ build_with(arel) if @values[:with]
66
+ arel
67
+ end
68
+
69
+ def build_with(arel) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
70
+ return if with_values.empty?
71
+
72
+ with_statements = with_values.map do |with_value|
73
+ case with_value
74
+ when String then Arel::Nodes::SqlLiteral.new(with_value)
75
+ when Arel::Nodes::As then with_value
76
+ when Hash then build_with_value_from_hash(with_value)
77
+ when Array then build_with_value_from_array(with_value)
78
+ else
79
+ raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}"
80
+ end
81
+ end
82
+
83
+ recursive_with? ? arel.with(:recursive, with_statements) : arel.with(with_statements)
84
+ end
85
+
86
+ def build_with_value_from_array(array)
87
+ unless array.map(&:class).uniq == [Arel::Nodes::As]
88
+ raise ArgumentError, "Unsupported argument type: #{array} #{array.class}"
89
+ end
90
+
91
+ array
92
+ end
93
+
94
+ def build_with_value_from_hash(hash) # rubocop:disable Metrics/MethodLength
95
+ hash.map do |name, value|
96
+ table = Arel::Table.new(name)
97
+ expression = case value
98
+ when String then Arel::Nodes::SqlLiteral.new("(#{value})")
99
+ when ActiveRecord::Relation then value.arel
100
+ when Arel::SelectManager, Arel::Nodes::Union then value
101
+ else
102
+ raise ArgumentError, "Unsupported argument type: #{value} #{value.class}"
103
+ end
104
+ Arel::Nodes::As.new(table, expression)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,114 @@
1
+ module AhoyCaptain
2
+ module Ahoy
3
+ module EventMethods
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ ransacker :route do |parent|
8
+ Arel.sql(AhoyCaptain.config.event[:url_column].gsub("properties", "ahoy_events.properties"))
9
+ end
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
+ }
47
+
48
+ scope :route_not_in, ->(*args) {
49
+ url_not_in(*args)
50
+ }
51
+
52
+ scope :route_i_cont, ->(arg) {
53
+ url_i_cont(arg)
54
+ }
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")
61
+ }
62
+
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
+ }
68
+
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")
73
+ }
74
+
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")
80
+ }
81
+
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")
87
+ }
88
+
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")
94
+ }
95
+
96
+ end
97
+
98
+ class_methods do
99
+ 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
101
+ end
102
+
103
+ 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]
105
+ end
106
+
107
+ def ransackable_associations(auth_object = nil)
108
+ super + [:visit]
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
@@ -0,0 +1,23 @@
1
+ module AhoyCaptain
2
+ module Ahoy
3
+ module VisitMethods
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ ransacker :ref_domain do
8
+ Arel.sql("(substring(referring_domain from '(?:.*://)?(?:www\.)?([^/?]*)'))")
9
+ end
10
+ end
11
+
12
+ class_methods do
13
+ def ransackable_attributes(auth = nil)
14
+ columns_hash.keys + ["ref_domain"]
15
+ end
16
+
17
+ def ransackable_associations(auth = nil)
18
+ super + ["events"]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+ require 'ahoy_captain/period_collection'
2
+
3
+ module AhoyCaptain
4
+ class Configuration
5
+ attr_accessor :view_name, :theme
6
+ attr_reader :goals, :funnels, :cache, :ranges, :disabled_widgets, :event, :models
7
+ def initialize
8
+ @goals = GoalCollection.new
9
+ @funnels = FunnelCollection.new
10
+ @theme = "dark"
11
+ @ranges = ::AhoyCaptain::PeriodCollection.load_default
12
+ @cache = ActiveSupport::OrderedOptions.new.tap do |option|
13
+ option.enabled = false
14
+ option.store = Rails.cache
15
+ option.ttl = 1.minute
16
+ end
17
+ @event = ActiveSupport::OrderedOptions.new.tap do |option|
18
+ option.view_name = "$view"
19
+ option.url_column = "CONCAT(properties->>'controller', '#', properties->>'action')"
20
+ option.url_exists = "JSONB_EXISTS(properties, 'controller') AND JSONB_EXISTS(properties, 'action')"
21
+ end
22
+ @models = ActiveSupport::OrderedOptions.new.tap do |option|
23
+ option.event = "::Ahoy::Event"
24
+ option.visit = "::Ahoy::Visit"
25
+ end
26
+ @disabled_widgets = []
27
+ end
28
+
29
+ def goal(id, &block)
30
+ instance = Goal.new
31
+ instance.id = id
32
+ instance.instance_exec(&block)
33
+ @goals.register(instance)
34
+ end
35
+
36
+ def funnel(id, &block)
37
+ instance = Funnel.new
38
+ instance.id = id
39
+ instance.instance_exec(&block)
40
+ @funnels.register(instance)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ require 'ahoy'
2
+ require 'turbo-rails'
3
+ require 'stimulus-rails'
4
+ require 'ransack'
5
+ require 'view_component'
6
+ require 'chartkick'
7
+ require 'groupdate'
8
+ require 'pagy'
9
+
10
+ module AhoyCaptain
11
+ class Engine < Rails::Engine
12
+ isolate_namespace ::AhoyCaptain
13
+
14
+ initializer "ahoy_captain.precompile" do |app|
15
+ app.config.assets.paths << AhoyCaptain::Engine.root.join("app/javascript")
16
+ app.config.assets.paths << AhoyCaptain::Engine.root.join("app/images")
17
+ app.config.assets.precompile << "ahoy_captain/application.js"
18
+ end
19
+
20
+ ActiveSupport.on_load(:active_record) do
21
+ require "ahoy_captain/active_record"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ module AhoyCaptain
2
+ class Funnel
3
+ attr_accessor :id
4
+ attr_reader :goals
5
+
6
+ def initialize
7
+ @id = nil
8
+ @label = nil
9
+ @goals = []
10
+ end
11
+
12
+ def goal(id)
13
+ @goals << AhoyCaptain.config.goals[id]
14
+ end
15
+
16
+ def label(value)
17
+ @label = value
18
+ end
19
+
20
+ def title
21
+ @label
22
+ end
23
+ end
24
+
25
+ class FunnelCollection
26
+ include Enumerable
27
+
28
+ def initialize
29
+ @funnels = {}
30
+ end
31
+
32
+ def register(funnel)
33
+ @funnels[funnel.id] = funnel
34
+ end
35
+
36
+ def each(&block)
37
+ @funnels.each(&block)
38
+ end
39
+
40
+ def [](value)
41
+ @funnels[value.to_sym]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ module AhoyCaptain
2
+ class Goal
3
+ attr_accessor :id
4
+ attr_reader :title, :event_name
5
+ def initialize
6
+ @id = nil
7
+ @title = nil
8
+ @event_name = nil
9
+ end
10
+
11
+ def label(value)
12
+ @title = value
13
+ end
14
+
15
+ def event(value)
16
+ @event_name = value
17
+ end
18
+ end
19
+
20
+ class GoalCollection
21
+ include Enumerable
22
+
23
+ def initialize
24
+ @goals = {}
25
+ end
26
+
27
+ def register(goal)
28
+ @goals[goal.id] = goal
29
+ end
30
+
31
+ def each(&block)
32
+ @goals.values.each(&block)
33
+ end
34
+
35
+ def [](value)
36
+ @goals[value]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,115 @@
1
+ module AhoyCaptain
2
+ class PeriodCollection
3
+ class Period
4
+ attr_reader :param, :label, :range
5
+ def initialize(param:, label:, range:)
6
+ @param = param
7
+ @label = label
8
+ @range = range
9
+ end
10
+ end
11
+
12
+ def self.load_default
13
+ instance = new
14
+ {
15
+ realtime: {
16
+ label: "Realtime",
17
+ range: -> { [1.minute.ago] },
18
+ },
19
+ day: {
20
+ label: "Day",
21
+ range: -> { [Time.current.beginning_of_day, Time.current] },
22
+ },
23
+ '7d': {
24
+ label: "7 Days",
25
+ range: -> { [7.days.ago, Time.current] },
26
+ },
27
+ '30d': {
28
+ label: "30 Days",
29
+ range: -> { [30.days.ago, Time.current] },
30
+ },
31
+ mtd: {
32
+ label: "Month-to-date",
33
+ range: -> { [Time.current.beginning_of_month, Time.current] },
34
+ },
35
+ lastmonth: {
36
+ label: "Last month",
37
+ range: -> { [1.month.ago.beginning_of_month, 1.month.ago.end_of_month] },
38
+ },
39
+ ytd: {
40
+ label: "This year",
41
+ range: -> { [Time.current.beginning_of_year, Time.current] },
42
+ },
43
+ '12mo': {
44
+ label: "12 months",
45
+ range: -> { [12.months.ago.to_datetime, Time.current] },
46
+ },
47
+ all: {
48
+ label: "All-time",
49
+ range: -> { [Date.new(2004, 8, 1).to_datetime, Time.current] },
50
+ },
51
+ }.each do |param, options|
52
+ instance.add(param, options[:label], options[:range])
53
+ end
54
+ instance.default = :mtd
55
+ instance
56
+ end
57
+
58
+ attr_reader :default, :max
59
+ def initialize
60
+ @periods = {}
61
+ @default = nil
62
+ @max = nil
63
+ end
64
+
65
+ def add(param, label, range_proc)
66
+ raise ArgumentError, "range must be a proc, or respond to .call" unless range_proc.respond_to?(:call)
67
+ raise ArgumentError, "range.call must return a range or an array" unless range_proc.call.is_a?(Range) || range_proc.call.is_a?(Array)
68
+
69
+ @periods[param.to_sym] = Period.new(param: param, label: label, range: range_proc)
70
+ end
71
+
72
+ def delete(param)
73
+ @periods.delete(param.to_sym)
74
+ end
75
+
76
+ def reset
77
+ @periods = {}
78
+ @default = nil
79
+ end
80
+
81
+ def each(&block)
82
+ @periods.each(&block)
83
+ end
84
+
85
+ def find(value)
86
+ @periods[value.try(:to_sym)]
87
+ end
88
+
89
+ def all
90
+ @periods
91
+ end
92
+
93
+ def default=(val)
94
+ @default = val.to_sym
95
+ end
96
+
97
+ def max=(amount)
98
+ @max = amount
99
+ end
100
+
101
+ def for(value)
102
+ if value.nil?
103
+ period = @periods[@default]
104
+ else
105
+ period = (@periods[value.to_sym] || @periods[@default])
106
+ end
107
+
108
+ if period
109
+ period.range.call
110
+ else
111
+ nil
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,7 @@
1
+ module AhoyCaptain
2
+ class Railtie < ::Rails::Railtie
3
+ initializer "ahoy_captain.assets.precompile" do |app|
4
+ app.config.assets.precompile += %w[ahoy_captain/manifest]
5
+ end
6
+ end
7
+ end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module AhoyCaptain
4
- VERSION = "0.1.0"
2
+ VERSION = "0.76"
5
3
  end
data/lib/ahoy_captain.rb CHANGED
@@ -1,8 +1,54 @@
1
- # frozen_string_literal: true
1
+ require "ahoy_captain/version"
2
+ require "ahoy_captain/railtie"
3
+ require "ahoy_captain/engine"
4
+ require "ahoy_captain/goals"
5
+ require "ahoy_captain/funnels"
6
+ require "ahoy_captain/configuration"
7
+ require 'ahoy_captain/ahoy/visit_methods'
8
+ require 'ahoy_captain/ahoy/event_methods'
2
9
 
3
- require_relative "ahoy_captain/version"
10
+ require 'importmap-rails'
4
11
 
5
12
  module AhoyCaptain
6
- class Error < StandardError; end
7
- # Your code goes here...
13
+ class << self
14
+ attr_accessor :configuration
15
+
16
+ def cache
17
+ @cache ||= if config.cache[:enabled]
18
+ config.cache[:store]
19
+ else
20
+ ActiveSupport::Cache::NullStore.new
21
+ end
22
+ end
23
+
24
+ def importmap
25
+ Importmap::Map.new.draw do
26
+ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
27
+ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
28
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
29
+ pin "application", to: "ahoy_captain/application.js", preload: true
30
+ pin "slim-select", to: "https://ga.jspm.io/npm:slim-select@2.6.0/dist/slimselect.es.js", preload: true
31
+ pin "chartkick", to: "chartkick.js"
32
+ pin "Chart.bundle", to: "Chart.bundle.js"
33
+ pin "chartjs-plugin-datalabels", to: "https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2", preload: true
34
+ pin_all_from AhoyCaptain::Engine.root.join("app/assets/javascript/ahoy_captain/controllers"), under: "controllers", to: "ahoy_captain/controllers"
35
+ end
36
+ end
37
+
38
+ def config
39
+ self.configuration ||= Configuration.new
40
+ end
41
+
42
+ def configure
43
+ yield config
44
+ end
45
+
46
+ def event
47
+ @event ||= config.models[:event].constantize
48
+ end
49
+
50
+ def visit
51
+ @visit ||= config.models[:visit].constantize
52
+ end
53
+ end
8
54
  end
@@ -0,0 +1,18 @@
1
+ require "rails/generators"
2
+
3
+ module AhoyCaptain
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.join(__dir__, "templates")
7
+
8
+ def copy_templates
9
+ insert_into_file ::Rails.root.join("app/models/ahoy/event.rb").to_s, " include AhoyCaptain::Ahoy::EventMethods\n", after: "class Ahoy::Event < ApplicationRecord\n"
10
+ insert_into_file ::Rails.root.join("app/models/ahoy/visit.rb").to_s, " include AhoyCaptain::Ahoy::VisitMethods\n", after: "class Ahoy::Visit < ApplicationRecord\n"
11
+
12
+ template "config.rb", "config/initializers/ahoy_captain.rb"
13
+
14
+ route "mount AhoyCaptain::Engine => '/ahoy_captain'"
15
+ end
16
+ end
17
+ end
18
+ end