ahoy_captain 0.11.1 → 0.76

Sign up to get free protection for your applications and to get access to all the features.
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)