ahoy_captain 0.11.1 → 0.76

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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -25
  3. data/Rakefile +2 -23
  4. data/app/assets/javascript/ahoy_captain/application.js +4 -4
  5. data/app/assets/javascript/ahoy_captain/controllers/application.js +5 -5
  6. data/app/assets/javascript/ahoy_captain/controllers/application_controller.js +8 -27
  7. data/app/assets/javascript/ahoy_captain/controllers/details_modal_controller.js +5 -5
  8. data/app/assets/javascript/ahoy_captain/controllers/dropdown_label_controller.js +2 -2
  9. data/app/assets/javascript/ahoy_captain/controllers/filter_controller.js +145 -0
  10. data/app/assets/javascript/ahoy_captain/controllers/filter_tag_controller.js +17 -0
  11. data/app/assets/javascript/ahoy_captain/controllers/funnel_chart_controller.js +127 -113
  12. data/app/assets/javascript/ahoy_captain/controllers/index.js +3 -4
  13. data/app/assets/javascript/ahoy_captain/controllers/link_controller.js +43 -0
  14. data/app/assets/javascript/ahoy_captain/controllers/navigation_controller.js +25 -0
  15. data/app/assets/javascript/ahoy_captain/controllers/realtime_controller.js +9 -12
  16. data/app/components/ahoy_captain/dropdown_button_component.html.erb +5 -5
  17. data/app/components/ahoy_captain/dropdown_link_component.html.erb +7 -5
  18. data/app/components/ahoy_captain/dropdown_link_component.rb +0 -4
  19. data/app/components/ahoy_captain/filter/modal_component.html.erb +9 -12
  20. data/app/components/ahoy_captain/filter/select_component.html.erb +19 -23
  21. data/app/components/ahoy_captain/filter/select_component.rb +9 -41
  22. data/app/components/ahoy_captain/filter/tag_component.html.erb +4 -8
  23. data/app/components/ahoy_captain/filter/tag_component.rb +30 -6
  24. data/app/components/ahoy_captain/filter/tag_container_component.html.erb +3 -2
  25. data/app/components/ahoy_captain/filter/tag_container_component.rb +8 -1
  26. data/app/components/ahoy_captain/sticky_nav_component.html.erb +33 -28
  27. data/app/components/ahoy_captain/sticky_nav_component.rb +0 -19
  28. data/app/components/ahoy_captain/table_component.html.erb +37 -4
  29. data/app/components/ahoy_captain/table_component.rb +5 -25
  30. data/app/components/ahoy_captain/tile_component.html.erb +10 -21
  31. data/app/components/ahoy_captain/tile_component.rb +2 -10
  32. data/app/components/ahoy_captain/tooltip_component.html.erb +2 -2
  33. data/app/controllers/ahoy_captain/application_controller.rb +30 -23
  34. data/app/controllers/ahoy_captain/campaigns_controller.rb +10 -2
  35. data/app/controllers/ahoy_captain/cities_controller.rb +24 -0
  36. data/app/controllers/ahoy_captain/countries_controller.rb +24 -0
  37. data/app/controllers/ahoy_captain/devices_controller.rb +6 -3
  38. data/app/controllers/ahoy_captain/entry_pages_controller.rb +4 -2
  39. data/app/controllers/ahoy_captain/exit_pages_controller.rb +4 -3
  40. data/app/controllers/ahoy_captain/filters/base_controller.rb +3 -1
  41. data/app/controllers/ahoy_captain/filters/pages/actions_controller.rb +1 -1
  42. data/app/controllers/ahoy_captain/filters/pages/entry_pages_controller.rb +3 -3
  43. data/app/controllers/ahoy_captain/filters/pages/exit_pages_controller.rb +3 -2
  44. data/app/controllers/ahoy_captain/filters/sources_controller.rb +1 -1
  45. data/app/controllers/ahoy_captain/filters/utms_controller.rb +1 -1
  46. data/app/controllers/ahoy_captain/realtimes_controller.rb +1 -1
  47. data/app/controllers/ahoy_captain/regions_controller.rb +24 -0
  48. data/app/controllers/ahoy_captain/sources_controller.rb +5 -2
  49. data/app/controllers/ahoy_captain/stats/base_controller.rb +0 -142
  50. data/app/controllers/ahoy_captain/stats/bounce_rates_controller.rb +2 -4
  51. data/app/controllers/ahoy_captain/stats/total_pageviews_controller.rb +1 -2
  52. data/app/controllers/ahoy_captain/stats/total_visits_controller.rb +1 -2
  53. data/app/controllers/ahoy_captain/stats/unique_visitors_controller.rb +1 -3
  54. data/app/controllers/ahoy_captain/stats/views_per_visits_controller.rb +8 -3
  55. data/app/controllers/ahoy_captain/stats/visit_durations_controller.rb +1 -2
  56. data/app/controllers/ahoy_captain/top_pages_controller.rb +8 -2
  57. data/app/decorators/ahoy_captain/application_decorator.rb +3 -27
  58. data/app/decorators/ahoy_captain/campaign_decorator.rb +0 -8
  59. data/app/decorators/ahoy_captain/city_decorator.rb +0 -12
  60. data/app/decorators/ahoy_captain/country_decorator.rb +0 -10
  61. data/app/decorators/ahoy_captain/device_decorator.rb +2 -13
  62. data/app/decorators/ahoy_captain/page_decorator.rb +0 -11
  63. data/app/decorators/ahoy_captain/region_decorator.rb +0 -16
  64. data/app/decorators/ahoy_captain/source_decorator.rb +0 -7
  65. data/app/helpers/ahoy_captain/application_helper.rb +3 -60
  66. data/app/models/ahoy_captain/current.rb +9 -0
  67. data/app/models/ahoy_captain/rangeable.rb +3 -0
  68. data/app/models/ahoy_captain/url_helpers.rb +6 -0
  69. data/app/models/concerns/ahoy_captain/range_options.rb +14 -1
  70. data/app/presenters/ahoy_captain/dashboard_presenter.rb +46 -18
  71. data/app/presenters/ahoy_captain/funnel_presenter.rb +29 -32
  72. data/app/presenters/ahoy_captain/goals_presenter.rb +23 -33
  73. data/app/queries/ahoy_captain/application_query.rb +13 -81
  74. data/app/queries/ahoy_captain/entry_pages_query.rb +2 -3
  75. data/app/queries/ahoy_captain/event_query.rb +8 -30
  76. data/app/queries/ahoy_captain/exit_pages_query.rb +4 -6
  77. data/app/queries/ahoy_captain/stats/average_views_per_visit_query.rb +4 -11
  78. data/app/queries/ahoy_captain/stats/average_visit_duration_query.rb +7 -15
  79. data/app/queries/ahoy_captain/stats/bounce_rates_query.rb +7 -24
  80. data/app/queries/ahoy_captain/stats/total_pageviews_query.rb +2 -2
  81. data/app/queries/ahoy_captain/stats/total_visitors_query.rb +2 -2
  82. data/app/queries/ahoy_captain/stats/unique_visitors_query.rb +2 -2
  83. data/app/queries/ahoy_captain/stats/views_per_visit_query.rb +3 -3
  84. data/app/queries/ahoy_captain/stats/visit_duration_query.rb +5 -5
  85. data/app/queries/ahoy_captain/visit_query.rb +13 -12
  86. data/app/views/ahoy_captain/devices/index.html+details.erb +1 -1
  87. data/app/views/ahoy_captain/devices/index.html.erb +2 -2
  88. data/app/views/ahoy_captain/funnels/show.html.erb +2 -5
  89. data/app/views/ahoy_captain/goals/index.html.erb +37 -2
  90. data/app/views/ahoy_captain/layouts/application.html.erb +4 -3
  91. data/app/views/ahoy_captain/realtimes/show.html.erb +1 -1
  92. data/app/views/ahoy_captain/roots/show.html.erb +118 -117
  93. data/app/views/ahoy_captain/stats/base/index.html.erb +1 -38
  94. data/app/views/ahoy_captain/stats/show.html.erb +56 -14
  95. data/config/routes.rb +3 -16
  96. data/lib/ahoy_captain/ahoy/event_methods.rb +75 -36
  97. data/lib/ahoy_captain/ahoy/visit_methods.rb +1 -1
  98. data/lib/ahoy_captain/configuration.rb +7 -18
  99. data/lib/ahoy_captain/engine.rb +0 -22
  100. data/lib/ahoy_captain/goals.rb +4 -16
  101. data/lib/ahoy_captain/period_collection.rb +1 -1
  102. data/lib/ahoy_captain/version.rb +1 -1
  103. data/lib/ahoy_captain.rb +1 -8
  104. data/lib/generators/ahoy_captain/templates/config.rb.tt +3 -50
  105. metadata +17 -185
  106. data/app/assets/javascript/ahoy_captain/controllers/combobox_controller.js +0 -371
  107. data/app/assets/javascript/ahoy_captain/controllers/filter/item_controller.js +0 -12
  108. data/app/assets/javascript/ahoy_captain/controllers/filter_form_controller.js +0 -13
  109. data/app/assets/javascript/ahoy_captain/controllers/filter_modal_controller.js +0 -45
  110. data/app/assets/javascript/ahoy_captain/controllers/frame_link_controller.js +0 -20
  111. data/app/assets/javascript/ahoy_captain/controllers/interval_controller.js +0 -15
  112. data/app/assets/javascript/ahoy_captain/controllers/line_chart_controller.js +0 -251
  113. data/app/assets/javascript/ahoy_captain/controllers/map_controller.js +0 -47
  114. data/app/assets/javascript/ahoy_captain/controllers/predicate_select_controller.js +0 -10
  115. data/app/assets/javascript/ahoy_captain/controllers/properties_controller.js +0 -8
  116. data/app/assets/javascript/ahoy_captain/controllers/property_filter_controller.js +0 -45
  117. data/app/assets/javascript/ahoy_captain/controllers/tile_controller.js +0 -33
  118. data/app/assets/javascript/ahoy_captain/controllers/toggle_controller.js +0 -17
  119. data/app/assets/javascript/ahoy_captain/helpers/chart_utils.js +0 -156
  120. data/app/assets/javascript/ahoy_captain/helpers/countries.js +0 -2261
  121. data/app/assets/javascript/ahoy_captain/helpers/number_formatters.js +0 -55
  122. data/app/components/ahoy_captain/combobox_component.html.erb +0 -33
  123. data/app/components/ahoy_captain/combobox_component.rb +0 -13
  124. data/app/components/ahoy_captain/comparison_link_component.html.erb +0 -17
  125. data/app/components/ahoy_captain/comparison_link_component.rb +0 -44
  126. data/app/components/ahoy_captain/filter/dropdown_component.html.erb +0 -50
  127. data/app/components/ahoy_captain/filter/dropdown_component.rb +0 -51
  128. data/app/components/ahoy_captain/previous_next_component.html.erb +0 -8
  129. data/app/components/ahoy_captain/previous_next_component.rb +0 -11
  130. data/app/components/ahoy_captain/stats/comparable_container_component.html.erb +0 -25
  131. data/app/components/ahoy_captain/stats/comparable_container_component.rb +0 -86
  132. data/app/components/ahoy_captain/stats/container_component.html.erb +0 -15
  133. data/app/components/ahoy_captain/stats/container_component.rb +0 -26
  134. data/app/components/ahoy_captain/tables/devices_table_component.rb +0 -11
  135. data/app/components/ahoy_captain/tables/dynamic_table.rb +0 -13
  136. data/app/components/ahoy_captain/tables/dynamic_table_component.rb +0 -204
  137. data/app/components/ahoy_captain/tables/goals_table_component.rb +0 -17
  138. data/app/components/ahoy_captain/tables/header_component.html.erb +0 -5
  139. data/app/components/ahoy_captain/tables/header_component.rb +0 -18
  140. data/app/components/ahoy_captain/tables/headers/header_component.html.erb +0 -5
  141. data/app/components/ahoy_captain/tables/headers/header_component.rb +0 -16
  142. data/app/components/ahoy_captain/tables/properties_table_component.rb +0 -27
  143. data/app/components/ahoy_captain/tables/row_component.html.erb +0 -4
  144. data/app/components/ahoy_captain/tables/rows/row_component.html.erb +0 -6
  145. data/app/components/ahoy_captain/tables/rows/row_component.rb +0 -40
  146. data/app/controllers/ahoy_captain/exports_controller.rb +0 -14
  147. data/app/controllers/ahoy_captain/filters/goals_controller.rb +0 -9
  148. data/app/controllers/ahoy_captain/filters/properties/names_controller.rb +0 -11
  149. data/app/controllers/ahoy_captain/filters/properties/values_controller.rb +0 -15
  150. data/app/controllers/ahoy_captain/locations/cities_controller.rb +0 -22
  151. data/app/controllers/ahoy_captain/locations/countries_controller.rb +0 -22
  152. data/app/controllers/ahoy_captain/locations/maps_controller.rb +0 -24
  153. data/app/controllers/ahoy_captain/locations/regions_controller.rb +0 -22
  154. data/app/controllers/ahoy_captain/properties_controller.rb +0 -41
  155. data/app/models/ahoy_captain/comparison_mode.rb +0 -72
  156. data/app/models/ahoy_captain/export.rb +0 -48
  157. data/app/models/ahoy_captain/filter_parser.rb +0 -82
  158. data/app/models/ahoy_captain/range_from_params.rb +0 -78
  159. data/app/models/concerns/ahoy_captain/compare_mode.rb +0 -19
  160. data/app/models/concerns/ahoy_captain/limitable.rb +0 -17
  161. data/app/queries/ahoy_captain/campaign_query.rb +0 -14
  162. data/app/queries/ahoy_captain/city_query.rb +0 -11
  163. data/app/queries/ahoy_captain/country_query.rb +0 -10
  164. data/app/queries/ahoy_captain/device_query.rb +0 -10
  165. data/app/queries/ahoy_captain/region_query.rb +0 -11
  166. data/app/queries/ahoy_captain/source_query.rb +0 -10
  167. data/app/queries/ahoy_captain/stats/base_query.rb +0 -18
  168. data/app/queries/ahoy_captain/top_page_query.rb +0 -13
  169. data/app/queries/concerns/ahoy_captain/comparable_queries.rb +0 -25
  170. data/app/queries/concerns/ahoy_captain/comparable_query.rb +0 -138
  171. data/app/queries/concerns/ahoy_captain/lazy_comparable_query.rb +0 -42
  172. data/app/views/ahoy_captain/devices/_table.html.erb +0 -2
  173. data/app/views/ahoy_captain/layouts/shared/_tile_loader.html.erb +0 -12
  174. data/app/views/ahoy_captain/locations/maps/show.html.erb +0 -3
  175. data/app/views/ahoy_captain/properties/_form.html.erb +0 -6
  176. data/app/views/ahoy_captain/properties/index.html.erb +0 -3
  177. data/app/views/ahoy_captain/properties/show.html.erb +0 -6
  178. data/app/views/ahoy_captain/roots/_filters.html.erb +0 -80
  179. data/lib/ahoy_captain/filter_configuration/filter.rb +0 -16
  180. data/lib/ahoy_captain/filter_configuration/filter_collection.rb +0 -48
  181. data/lib/ahoy_captain/filters_configuration.rb +0 -77
  182. data/lib/ahoy_captain/predicate_label.rb +0 -7
  183. data/lib/generators/ahoy_captain/migration_generator.rb +0 -21
  184. data/lib/generators/ahoy_captain/templates/migration.rb.tt +0 -7
  185. /data/app/views/ahoy_captain/{locations/cities → cities}/index.html+details.erb +0 -0
  186. /data/app/views/ahoy_captain/{locations/cities → cities}/index.html.erb +0 -0
  187. /data/app/views/ahoy_captain/{locations/countries → countries}/index.html+details.erb +0 -0
  188. /data/app/views/ahoy_captain/{locations/countries → countries}/index.html.erb +0 -0
  189. /data/app/views/ahoy_captain/{locations/regions → regions}/index.html+details.erb +0 -0
  190. /data/app/views/ahoy_captain/{locations/regions → regions}/index.html.erb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 550735e026fe60170552c09149ee5dceb1d874020c131f49c9a79a6d1c04982f
4
- data.tar.gz: 9a782cddeb96ba6726980ae8f0c5b332f51756b8114b88dc63af91a2739776ea
3
+ metadata.gz: bdbe217ea7df263239572663d05816b79847f65f7c4ec9dfa120749c0f5b45d3
4
+ data.tar.gz: 6176d55f8d2502a46891032ddfd51043fe4c4e7bd5efb9ac754d7e1576e4f866
5
5
  SHA512:
6
- metadata.gz: 021c101c8ff731e860b85baab37836fdb9f1dc5a038c90cd5411b9f0e628b8918823fdc7446164620414bce1a20560a7284dd6d63753ad4ab6c1f1541aed17d8
7
- data.tar.gz: b0a5fc48678367faa56299f2462b92edf3331adc4df3f0cc685b68584cfea4604d76437528007ce27e5c3173312945d514e2399a65efad1d4b473e2a134b6690
6
+ metadata.gz: 6ec21ec3864bfb7b6d87fa65df0b1cb67879cd8cdd9b25c246bec2ccf9dac72d9858bfef19e9d0fb590dac11d5cb4dad491d81f5a81ca87532254051c2cb335b
7
+ data.tar.gz: 9a0a6fba58462a0aaea1ee6d5536e21c7ca5c40c4332c89688ae99409e94495ee7215b31aa948ee9123b10f821b9405742f3f4946ee5c50b96f156476f6ae80a
data/README.md CHANGED
@@ -1,12 +1,17 @@
1
- # <img src="logo.png" style="max-height:100px" /> AhoyCaptain
1
+ # AhoyCaptain
2
2
 
3
+ <img src="logo.png" style="max-width:100px" />
3
4
 
4
5
  A full-featured, mountable analytics dashboard for your Rails app, shamelessly inspired by Plausible Analytics, powered by the Ahoy gem.
5
6
 
6
7
  <a href="https://github.com/joshmn/ahoy_captain/blob/main/ss.jpg"><img src="ss.jpg" style="max-width:300px" /></a>
7
8
  ## Notice
8
9
 
9
- Currently requires using PG and a JSONB column for your data.
10
+ While this is fine to use in production, it was only built against a PostgreSQL instance. Some of the queries are certainly broken.
11
+
12
+ ## Some assumptions
13
+
14
+ Some hardcoded stuff as of writing; this will be more fully-featured in due time.
10
15
 
11
16
  ## Installation
12
17
 
@@ -24,26 +29,11 @@ $ bundle add ahoy_captain
24
29
  $ rails g ahoy_captain:install
25
30
  ```
26
31
 
27
- ### 3. Make sure your events are setup correctly
28
-
29
- AhoyCaptain doesn't do any tracking for you; it merely provides a dashboard for your data from the Ahoy gem.
30
-
31
- By default, AhoyCaptain assumes you're tracking `controller` and `action` in your `Ahoy::Event` properties, and a page view event is named `$view`. See this section for more information: https://github.com/ankane/ahoy#events
32
-
33
- For a quick sanity check:
34
-
35
- ```ruby
36
- AhoyCaptain.event.where(name: AhoyCaptain.config.event[:view_name]).count
37
- AhoyCaptain.event.with_routes.count
38
- ```
39
-
40
- This can be fully-customized. See the initializer `config/initializers/ahoy_captain.rb` for more.
41
-
42
- ### 4. Star this repo
32
+ ### 3. Star this repo
43
33
 
44
34
  No, seriously, I need all the internet clout I can get.
45
35
 
46
- ### 5. Analyze your nightmares
36
+ ### 4. Analyze your nightmares
47
37
 
48
38
  If you have a large dataset (> 1GB) you probably want some indexes. `rails g ahoy_captain:migration`
49
39
 
@@ -62,14 +52,12 @@ If you have a large dataset (> 1GB) you probably want some indexes. `rails g aho
62
52
  * Device type
63
53
  * OS
64
54
  * UTM tags
65
- * Goal
66
- * Event Property
67
- * CSV exports
68
- * Date comparison
69
55
 
70
56
  ## Coming soon ™️
71
57
 
72
- * Bug fixes and performance improvements
58
+ * Date comparison
59
+ * More filters
60
+ * CSV exports
73
61
 
74
62
  ## Contributors
75
63
 
@@ -77,7 +65,7 @@ This was built during the Rails Hackathon in July 2023 with [afogel](https://git
77
65
 
78
66
  ## Contributions
79
67
 
80
- Do your worst; please and thank you in advance! :)
68
+ Please and thank you in advance!
81
69
 
82
70
  ## License
83
71
 
data/Rakefile CHANGED
@@ -1,24 +1,3 @@
1
- # frozen_string_literal: true
1
+ require "bundler/setup"
2
2
 
3
- begin
4
- require 'bundler/setup'
5
- rescue LoadError
6
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
- end
8
-
9
- require 'rdoc/task'
10
-
11
- RDoc::Task.new(:rdoc) do |rdoc|
12
- rdoc.rdoc_dir = 'rdoc'
13
- rdoc.title = 'Caffeinate'
14
- rdoc.options << '--line-numbers'
15
- rdoc.rdoc_files.include('README.md')
16
- rdoc.rdoc_files.include('lib/**/*.rb')
17
- end
18
-
19
- APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
20
- load 'rails/tasks/engine.rake'
21
-
22
- load 'rails/tasks/statistics.rake'
23
-
24
- require 'bundler/gem_tasks'
3
+ require "bundler/gem_tasks"
@@ -1,4 +1,4 @@
1
- import '@hotwired/turbo-rails';
2
- import 'controllers';
3
- import 'chartkick';
4
- import 'Chart.bundle';
1
+ import "@hotwired/turbo-rails"
2
+ import "controllers"
3
+ import "chartkick"
4
+ import "Chart.bundle"
@@ -1,9 +1,9 @@
1
- import { Application } from '@hotwired/stimulus';
1
+ import { Application } from "@hotwired/stimulus"
2
2
 
3
- const application = Application.start();
3
+ const application = Application.start()
4
4
 
5
5
  // Configure Stimulus development experience
6
- application.debug = false;
7
- window.Stimulus = application;
6
+ application.debug = false
7
+ window.Stimulus = application
8
8
 
9
- export { application };
9
+ export { application }
@@ -1,33 +1,14 @@
1
- import { Controller } from '@hotwired/stimulus';
1
+ import {Controller} from "@hotwired/stimulus"
2
2
 
3
3
  export default class extends Controller {
4
- connect() {
5
- window.comboboxConnected = 0;
6
- if (new URLSearchParams(window.location.search).get('period') === 'realtime') {
7
- this.element.querySelectorAll('turbo-frame').forEach((frame) => {
8
- setInterval(() => {
9
- frame.reload();
10
- }, 1000 * 30);
11
- });
12
- }
13
-
14
- document.querySelectorAll('a[data-turbo-frame]').forEach(link => {
15
- const frameSelector = link.dataset.turboFrame;
16
- const frame = document.querySelector(`turbo-frame#${frameSelector}`);
17
- if(frame) {
18
- const src = frame.src;
19
- if(link.href.includes(src)) {
20
- link.classList.add('text-primary', 'font-semibold')
4
+ connect() {
5
+ if(new URLSearchParams(window.location.search).get("period") === 'realtime') {
6
+ this.element.querySelectorAll('turbo-frame').forEach(frame => {
7
+ setInterval(() => {
8
+ frame.reload()
9
+ }, 1000 * 30);
10
+ })
21
11
  }
22
- }
23
-
24
- })
25
- }
26
-
27
- comboboxInit(event) {
28
- if(event.detail.combobox.selectTarget.id === "property-name" || event.detail.combobox.selectTarget.id === "property-value") {
29
- window.comboboxConnected += 1;
30
12
  }
31
- }
32
13
 
33
14
  }
@@ -1,15 +1,15 @@
1
- import { Controller } from '@hotwired/stimulus';
1
+ import {Controller} from "@hotwired/stimulus"
2
2
 
3
3
  export default class extends Controller {
4
4
  static values = {
5
- target: String,
6
- };
7
-
5
+ target: String
6
+ }
7
+
8
8
  connect() {
9
9
  this.modal = document.querySelector('#detailsModal');
10
10
  this.turboFrame = document.querySelector('#detailsModal turbo-frame');
11
11
  }
12
-
12
+
13
13
  openModal(e) {
14
14
  e.preventDefault();
15
15
  this.modal.showModal();
@@ -1,7 +1,7 @@
1
- import { Controller } from '@hotwired/stimulus';
1
+ import {Controller} from "@hotwired/stimulus"
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ['label', 'close'];
4
+ static targets = ["label", "close"]
5
5
 
6
6
  setLabel(event) {
7
7
  this.labelTarget.innerText = event.target.innerText;
@@ -0,0 +1,145 @@
1
+ import {Controller} from "@hotwired/stimulus"
2
+ import SlimSelect from 'slim-select'
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ url: String,
7
+ column: String,
8
+ };
9
+ static targets = ["select", 'predicate'];
10
+ connect() {
11
+ this.selectTargets.forEach(async (target) => {
12
+ const url = target.dataset.filterUrlValue;
13
+ const optionsSearch = this.fetchOptions(url, target);
14
+ const select = await new SlimSelect({
15
+ select: target,
16
+ data: [],
17
+ settings: {
18
+ contentPosition: 'relative',
19
+ contentLocation: target.closest('fieldset'),
20
+ searchText: 'Sorry, no results found',
21
+ searchPlaceholder: 'Type to populate results',
22
+ placeholderText: `Search for ${target.dataset.filterColumnValue}`,
23
+ searchHighlight: true
24
+ },
25
+ events: {
26
+ beforeOpen: async () => {
27
+ if (!this.#hasData(target) & !this.#hasSelections(target) ) {
28
+ const data = await optionsSearch();
29
+ target.slim.setData(data);
30
+ }
31
+ },
32
+ search: async (search, currentData) => {
33
+ const data = await optionsSearch(search);
34
+ const filteredData = data.filter(item => !currentData.some((selectedItem) => selectedItem.text == item.text ))
35
+ return filteredData;
36
+ },
37
+ beforeChange: this.#beforeChange(target, url),
38
+ }
39
+ });
40
+ const json = JSON.parse(target.dataset.filterSelected)
41
+ if(json.length) {
42
+ select.setData(json.map(item => ({ "text": item, "value": item })))
43
+ select.setSelected(json)
44
+ }
45
+ })
46
+ }
47
+
48
+ fetchOptions(url, target) {
49
+ return async (search) => {
50
+ const query = this.#buildQueryFilters(search, target);
51
+ const response = await fetch(`${url}?${query.toString()}`);
52
+ const data = await response.json();
53
+ return data;
54
+ }
55
+ }
56
+
57
+ #buildQueryFilters(search, target) {
58
+ const query = new URLSearchParams(window.location.search);
59
+ const otherFilters = this.selectTargets.filter(filter => filter != target);
60
+ otherFilters.forEach(function(filter) {
61
+ filter.slim.getSelected().forEach(val => {
62
+ const filterName = `${filter.name}[]`
63
+ if(query.has(filterName)) {
64
+ console.log("has the filter")
65
+ if(!query.get(`${filter.name}[]`).includes(val)) {
66
+ query.append(filterName, value)
67
+ }
68
+ } else {
69
+ query.append(filterName, value)
70
+ }
71
+ })
72
+ });
73
+ query.set(`q[${target.dataset.filterColumnValue}_i_cont]`, search || "");
74
+ return query;
75
+ }
76
+
77
+ #beforeChange(target, url) {
78
+ return () => {
79
+ const otherFilters = this.selectTargets.filter(filter => filter != target);
80
+ otherFilters.forEach(async target => {
81
+ if (this.#hasData(target)) {
82
+ const selected = target.slim.getSelected()
83
+ target.slim.setData(selected.map(text => ({ text })))
84
+ // setting data triggers a slim render, so need to also set selected again
85
+ target.slim.setSelected(selected)
86
+ }
87
+ })
88
+ return true;
89
+ }
90
+ }
91
+
92
+ #hasData(target) {
93
+ return target.slim.getData().length !== 0;
94
+ }
95
+
96
+ #hasSelections(target) {
97
+ return target.slim.getSelected().length !== 0;
98
+ }
99
+
100
+ #filtersForQuery() {
101
+ const predicates = this.predicateTargets.map(el => ({name: el.name, column_predicate: el.value}));
102
+ const filterValues = this.selectTargets.map(el => ({name: el.name, selections: el.slim.getSelected()}));
103
+ const mergedData = predicates.map(predicate => {
104
+ const matchingFilter = filterValues.find(filter => filter.name === predicate.name);
105
+ if (matchingFilter.selections.length > 0) {
106
+ return {...predicate, ...matchingFilter}
107
+ }
108
+ }).filter(el => el !== undefined);
109
+ return mergedData;
110
+ }
111
+
112
+ resetFilters(event) {
113
+ this.selectTargets.forEach(el => {
114
+ el.slim.setSelected([])
115
+ })
116
+ this.applyFilters(event)
117
+ }
118
+
119
+ applyFilters(e) {
120
+ e.preventDefault();
121
+ const searchParams = new URLSearchParams(window.location.search);
122
+ const filters = this.#filtersForQuery();
123
+ filters.forEach(filter => {
124
+ const name = `q[${filter.column_predicate}][]`
125
+ const selectedValues = searchParams.getAll(name)
126
+ filter.selections.forEach(selection => {
127
+ if(selectedValues) {
128
+ if(!selectedValues.includes(selection)) {
129
+ searchParams.append(name, selection)
130
+ }
131
+ } else {
132
+ searchParams.append(name, selection)
133
+ }
134
+ })
135
+ });
136
+ ['input[name="start_date"]', 'input[name="end_date"]'].forEach(selector => {
137
+ const el = document.querySelector(selector)
138
+ if(el.value.length) {
139
+ searchParams.delete("period")
140
+ }
141
+ searchParams.set(el.name, el.value);
142
+ });
143
+ Turbo.visit(window.location.pathname.replace(/\/$/, "") + `?${searchParams.toString()}`)
144
+ }
145
+ }
@@ -0,0 +1,17 @@
1
+ import {Controller} from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["delete"];
5
+ static values = {
6
+ columnPredicate: String,
7
+ category: String
8
+ }
9
+
10
+ remove(event) {
11
+ event.preventDefault();
12
+ this.dispatch('remove', {detail: {
13
+ paramKey: `q[${this.columnPredicateValue}]`,
14
+ paramValue: this.categoryValue
15
+ }})
16
+ }
17
+ }
@@ -1,129 +1,143 @@
1
- import { Controller } from '@hotwired/stimulus';
1
+ import {Controller} from "@hotwired/stimulus"
2
2
  import 'chartjs-plugin-datalabels';
3
- import { getCSS, externalTooltipHandler } from "helpers/chart_utils";
3
+ const THOUSAND = 1000
4
+ const HUNDRED_THOUSAND = 100000
5
+ const MILLION = 1000000
6
+ const HUNDRED_MILLION = 100000000
7
+ const BILLION = 1000000000
8
+ const HUNDRED_BILLION = 100000000000
9
+ const TRILLION = 1000000000000
4
10
 
5
- const calculatePercentageDifference = function(oldValue, newValue) {
6
- if(!oldValue) { return false }
7
- if (oldValue == 0 && newValue > 0) {
8
- return 100
9
- } else if (oldValue == 0 && newValue == 0) {
10
- return 0
11
- } else {
12
- return Math.round((newValue - oldValue) / oldValue * 100)
13
- }
11
+ function numberFormatter(num) {
12
+ if (num >= THOUSAND && num < MILLION) {
13
+ const thousands = num / THOUSAND
14
+ if (thousands === Math.floor(thousands) || num >= HUNDRED_THOUSAND) {
15
+ return Math.floor(thousands) + 'k'
16
+ } else {
17
+ return (Math.floor(thousands * 10) / 10) + 'k'
18
+ }
19
+ } else if (num >= MILLION && num < BILLION) {
20
+ const millions = num / MILLION
21
+ if (millions === Math.floor(millions) || num >= HUNDRED_MILLION) {
22
+ return Math.floor(millions) + 'M'
23
+ } else {
24
+ return (Math.floor(millions * 10) / 10) + 'M'
25
+ }
26
+ } else if (num >= BILLION && num < TRILLION) {
27
+ const billions = num / BILLION
28
+ if (billions === Math.floor(billions) || num >= HUNDRED_BILLION) {
29
+ return Math.floor(billions) + 'B'
30
+ } else {
31
+ return (Math.floor(billions * 10) / 10) + 'B'
32
+ }
33
+ } else {
34
+ return num
35
+ }
14
36
  }
15
37
 
16
38
  export default class extends Controller {
17
- connect() {
18
- this.funnel = JSON.parse(this.element.dataset.data);
19
-
20
- const fontFamily = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"';
21
- const labels = this.funnel.steps.map((step) => step.name);
22
- const stepData = this.funnel.steps.map((step) => step.total_events);
23
- const dropOffData = this.funnel.steps.map((step) => step.drop_off * 100);
24
-
25
- const data = {
26
- labels,
27
- datasets: [
28
- {
29
- label: 'Visitors',
30
- data: stepData,
31
- borderRadius: 4,
32
- color: getCSS('--ac'),
33
- backgroundColor: getCSS('--p'),
34
- stack: 'Stack 0',
35
- yAxisID: 'y',
36
- },
37
- {
38
- label: 'Dropoff',
39
- data: dropOffData,
40
- borderRadius: 4,
41
- stack: 'Stack 0',
42
- color: getCSS('--ac'),
43
- backgroundColor: getCSS('--a'),
44
- yAxisID: 'yComparison',
45
- },
46
- ],
47
- };
39
+ connect() {
40
+ const funnel = JSON.parse(this.element.dataset.data);
48
41
 
49
- const config = {
50
- responsive: true,
51
- maintainAspectRatio: false,
52
- plugins: [ChartDataLabels],
53
- type: 'bar',
54
- data,
55
- options: {
56
- layout: {
57
- padding: 100,
58
- },
59
- plugins: {
60
- legend: false,
61
- tooltip: {
62
- enabled: false,
63
- position: 'nearest',
64
- external: externalTooltipHandler(this)
65
- },
66
- datalabels: {
67
- anchor: 'end',
68
- align: 'end',
69
- borderRadius: 4,
70
- padding: {
71
- top: 8, bottom: 8, right: 8, left: 8,
72
- },
73
- color: getCSS('--pc'),
74
- textAlign: 'center',
75
- },
76
- },
77
- scales: {
78
- y: { display: false },
79
- x: {
80
- position: 'bottom',
81
- display: true,
82
- border: { display: false },
83
- grid: { drawBorder: false, display: false },
84
- ticks: {
85
- padding: 8,
86
- },
87
- },
88
- },
89
- },
90
- };
42
+ const getPalette = () => {
43
+ return {
44
+ dataLabelBackground: 'rgba(25, 30, 56, 0.97)',
45
+ dataLabelTextColor: 'rgb(243, 244, 246)',
46
+ visitorsBackground: 'rgb(99, 102, 241)',
47
+ dropoffBackground: '#2F3949',
48
+ dropoffStripes: 'rgb(25, 30, 56)',
49
+ stepNameLegendColor: 'rgb(228, 228, 231)',
50
+ visitorsLegendClass: 'bg-indigo-500',
51
+ dropoffLegendClass: 'bg-gray-600',
52
+ smallBarClass: 'bg-indigo-500'
53
+ }
54
+ }
91
55
 
92
- const visitorsData = [];
93
56
 
94
- this.chart = new Chart(
95
- this.element,
96
- config,
97
- );
98
- }
99
57
 
100
- formatLabel(label) {
101
- return label
102
- }
58
+ var fontFamily = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'
59
+ const calcBarThickness = (ctx) => {
60
+ if (ctx.dataset.data.length <= 3) {
61
+ return 160
62
+ } else {
63
+ return Math.floor(650 / ctx.dataset.data.length)
64
+ }
65
+ }
103
66
 
104
- formatMetric(metric) {
105
- return metric
106
- }
67
+ const labels = funnel.steps.map((step) => step.step)
68
+ const stepData = funnel.steps.map((step) => step.count)
69
+ const dropOffData = funnel.steps.map((step) => step.drop_off)
107
70
 
71
+ const data = {
72
+ labels: labels,
73
+ datasets: [
74
+ {
75
+ label: 'Visitors',
76
+ data: stepData,
77
+ borderRadius: 4,
78
+ stack: 'Stack 0',
79
+ },
80
+ {
81
+ label: 'Dropoff',
82
+ data: dropOffData,
83
+ borderRadius: 4,
84
+ stack: 'Stack 0',
85
+ },
86
+ ],
87
+ }
108
88
 
109
- extractTooltipData(tooltip) {
110
- const data = this.funnel.steps.find(step => step.name === tooltip.title[0]);
89
+ console.log(data)
90
+ const config = {
91
+ responsive:true,
92
+ maintainAspectRatio: false,
93
+ plugins: [ChartDataLabels],
94
+ type: 'bar',
95
+ data: data,
96
+ options: {
97
+ layout: {
98
+ padding: 100,
99
+ },
100
+ barThickness: calcBarThickness,
101
+ plugins: {
102
+ legend: {
103
+ display: false,
104
+ },
105
+ tooltip: {
106
+ mode: 'index',
107
+ intersect: true,
108
+ position: 'average',
109
+ },
110
+ datalabels: {
111
+ anchor: 'end',
112
+ align: 'end',
113
+ borderRadius: 4,
114
+ padding: { top: 8, bottom: 8, right: 8, left: 8 },
115
+ font: { size: 12, weight: 'normal', lineHeight: 1.6, family: fontFamily },
116
+ textAlign: 'center',
117
+ },
118
+ },
119
+ scales: {
120
+ y: { display: false },
121
+ x: {
122
+ position: 'bottom',
123
+ display: true,
124
+ border: { display: false },
125
+ grid: { drawBorder: false, display: false },
126
+ ticks: {
127
+ padding: 8,
128
+ font: { weight: 'bold', family: fontFamily, size: 14 },
129
+ color: 'rgb(228, 228, 231)'
130
+ },
131
+ },
132
+ },
133
+ },
134
+ }
111
135
 
112
- const value = data.total_events;
113
- const label = "Visitors"
114
- let comparisonLabel = "Dropoff"
115
- let comparisonValue = data.unique_visits;
136
+ const visitorsData = []
116
137
 
117
- return {
118
- comparison: true,
119
- comparisonDifference: false,
120
- metric: tooltip.title[0],
121
- label: this.formatLabel(label),
122
- labelBackgroundColor: getCSS('--bc'),
123
- formattedValue: value,
124
- comparisonLabel: comparisonLabel,
125
- comparisonLabelBackgroundColor: "",
126
- formattedComparisonValue: comparisonValue,
138
+ new Chart(
139
+ this.element,
140
+ config
141
+ );
127
142
  }
128
- }
129
143
  }
@@ -1,4 +1,3 @@
1
- import { application } from 'controllers/application';
2
- import { eagerLoadControllersFrom } from '@hotwired/stimulus-loading';
3
-
4
- eagerLoadControllersFrom('controllers', application);
1
+ import {application} from "controllers/application"
2
+ import {eagerLoadControllersFrom} from "@hotwired/stimulus-loading"
3
+ eagerLoadControllersFrom("controllers", application)