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.
- checksums.yaml +4 -4
- data/README.md +13 -25
- data/Rakefile +2 -23
- data/app/assets/javascript/ahoy_captain/application.js +4 -4
- data/app/assets/javascript/ahoy_captain/controllers/application.js +5 -5
- data/app/assets/javascript/ahoy_captain/controllers/application_controller.js +8 -27
- data/app/assets/javascript/ahoy_captain/controllers/details_modal_controller.js +5 -5
- data/app/assets/javascript/ahoy_captain/controllers/dropdown_label_controller.js +2 -2
- data/app/assets/javascript/ahoy_captain/controllers/filter_controller.js +145 -0
- data/app/assets/javascript/ahoy_captain/controllers/filter_tag_controller.js +17 -0
- data/app/assets/javascript/ahoy_captain/controllers/funnel_chart_controller.js +127 -113
- data/app/assets/javascript/ahoy_captain/controllers/index.js +3 -4
- data/app/assets/javascript/ahoy_captain/controllers/link_controller.js +43 -0
- data/app/assets/javascript/ahoy_captain/controllers/navigation_controller.js +25 -0
- data/app/assets/javascript/ahoy_captain/controllers/realtime_controller.js +9 -12
- data/app/components/ahoy_captain/dropdown_button_component.html.erb +5 -5
- data/app/components/ahoy_captain/dropdown_link_component.html.erb +7 -5
- data/app/components/ahoy_captain/dropdown_link_component.rb +0 -4
- data/app/components/ahoy_captain/filter/modal_component.html.erb +9 -12
- data/app/components/ahoy_captain/filter/select_component.html.erb +19 -23
- data/app/components/ahoy_captain/filter/select_component.rb +9 -41
- data/app/components/ahoy_captain/filter/tag_component.html.erb +4 -8
- data/app/components/ahoy_captain/filter/tag_component.rb +30 -6
- data/app/components/ahoy_captain/filter/tag_container_component.html.erb +3 -2
- data/app/components/ahoy_captain/filter/tag_container_component.rb +8 -1
- data/app/components/ahoy_captain/sticky_nav_component.html.erb +33 -28
- data/app/components/ahoy_captain/sticky_nav_component.rb +0 -19
- data/app/components/ahoy_captain/table_component.html.erb +37 -4
- data/app/components/ahoy_captain/table_component.rb +5 -25
- data/app/components/ahoy_captain/tile_component.html.erb +10 -21
- data/app/components/ahoy_captain/tile_component.rb +2 -10
- data/app/components/ahoy_captain/tooltip_component.html.erb +2 -2
- data/app/controllers/ahoy_captain/application_controller.rb +30 -23
- data/app/controllers/ahoy_captain/campaigns_controller.rb +10 -2
- data/app/controllers/ahoy_captain/cities_controller.rb +24 -0
- data/app/controllers/ahoy_captain/countries_controller.rb +24 -0
- data/app/controllers/ahoy_captain/devices_controller.rb +6 -3
- data/app/controllers/ahoy_captain/entry_pages_controller.rb +4 -2
- data/app/controllers/ahoy_captain/exit_pages_controller.rb +4 -3
- data/app/controllers/ahoy_captain/filters/base_controller.rb +3 -1
- data/app/controllers/ahoy_captain/filters/pages/actions_controller.rb +1 -1
- data/app/controllers/ahoy_captain/filters/pages/entry_pages_controller.rb +3 -3
- data/app/controllers/ahoy_captain/filters/pages/exit_pages_controller.rb +3 -2
- data/app/controllers/ahoy_captain/filters/sources_controller.rb +1 -1
- data/app/controllers/ahoy_captain/filters/utms_controller.rb +1 -1
- data/app/controllers/ahoy_captain/realtimes_controller.rb +1 -1
- data/app/controllers/ahoy_captain/regions_controller.rb +24 -0
- data/app/controllers/ahoy_captain/sources_controller.rb +5 -2
- data/app/controllers/ahoy_captain/stats/base_controller.rb +0 -142
- data/app/controllers/ahoy_captain/stats/bounce_rates_controller.rb +2 -4
- data/app/controllers/ahoy_captain/stats/total_pageviews_controller.rb +1 -2
- data/app/controllers/ahoy_captain/stats/total_visits_controller.rb +1 -2
- data/app/controllers/ahoy_captain/stats/unique_visitors_controller.rb +1 -3
- data/app/controllers/ahoy_captain/stats/views_per_visits_controller.rb +8 -3
- data/app/controllers/ahoy_captain/stats/visit_durations_controller.rb +1 -2
- data/app/controllers/ahoy_captain/top_pages_controller.rb +8 -2
- data/app/decorators/ahoy_captain/application_decorator.rb +3 -27
- data/app/decorators/ahoy_captain/campaign_decorator.rb +0 -8
- data/app/decorators/ahoy_captain/city_decorator.rb +0 -12
- data/app/decorators/ahoy_captain/country_decorator.rb +0 -10
- data/app/decorators/ahoy_captain/device_decorator.rb +2 -13
- data/app/decorators/ahoy_captain/page_decorator.rb +0 -11
- data/app/decorators/ahoy_captain/region_decorator.rb +0 -16
- data/app/decorators/ahoy_captain/source_decorator.rb +0 -7
- data/app/helpers/ahoy_captain/application_helper.rb +3 -60
- data/app/models/ahoy_captain/current.rb +9 -0
- data/app/models/ahoy_captain/rangeable.rb +3 -0
- data/app/models/ahoy_captain/url_helpers.rb +6 -0
- data/app/models/concerns/ahoy_captain/range_options.rb +14 -1
- data/app/presenters/ahoy_captain/dashboard_presenter.rb +46 -18
- data/app/presenters/ahoy_captain/funnel_presenter.rb +29 -32
- data/app/presenters/ahoy_captain/goals_presenter.rb +23 -33
- data/app/queries/ahoy_captain/application_query.rb +13 -81
- data/app/queries/ahoy_captain/entry_pages_query.rb +2 -3
- data/app/queries/ahoy_captain/event_query.rb +8 -30
- data/app/queries/ahoy_captain/exit_pages_query.rb +4 -6
- data/app/queries/ahoy_captain/stats/average_views_per_visit_query.rb +4 -11
- data/app/queries/ahoy_captain/stats/average_visit_duration_query.rb +7 -15
- data/app/queries/ahoy_captain/stats/bounce_rates_query.rb +7 -24
- data/app/queries/ahoy_captain/stats/total_pageviews_query.rb +2 -2
- data/app/queries/ahoy_captain/stats/total_visitors_query.rb +2 -2
- data/app/queries/ahoy_captain/stats/unique_visitors_query.rb +2 -2
- data/app/queries/ahoy_captain/stats/views_per_visit_query.rb +3 -3
- data/app/queries/ahoy_captain/stats/visit_duration_query.rb +5 -5
- data/app/queries/ahoy_captain/visit_query.rb +13 -12
- data/app/views/ahoy_captain/devices/index.html+details.erb +1 -1
- data/app/views/ahoy_captain/devices/index.html.erb +2 -2
- data/app/views/ahoy_captain/funnels/show.html.erb +2 -5
- data/app/views/ahoy_captain/goals/index.html.erb +37 -2
- data/app/views/ahoy_captain/layouts/application.html.erb +4 -3
- data/app/views/ahoy_captain/realtimes/show.html.erb +1 -1
- data/app/views/ahoy_captain/roots/show.html.erb +118 -117
- data/app/views/ahoy_captain/stats/base/index.html.erb +1 -38
- data/app/views/ahoy_captain/stats/show.html.erb +56 -14
- data/config/routes.rb +3 -16
- data/lib/ahoy_captain/ahoy/event_methods.rb +75 -36
- data/lib/ahoy_captain/ahoy/visit_methods.rb +1 -1
- data/lib/ahoy_captain/configuration.rb +7 -18
- data/lib/ahoy_captain/engine.rb +0 -22
- data/lib/ahoy_captain/goals.rb +4 -16
- data/lib/ahoy_captain/period_collection.rb +1 -1
- data/lib/ahoy_captain/version.rb +1 -1
- data/lib/ahoy_captain.rb +1 -8
- data/lib/generators/ahoy_captain/templates/config.rb.tt +3 -50
- metadata +17 -185
- data/app/assets/javascript/ahoy_captain/controllers/combobox_controller.js +0 -371
- data/app/assets/javascript/ahoy_captain/controllers/filter/item_controller.js +0 -12
- data/app/assets/javascript/ahoy_captain/controllers/filter_form_controller.js +0 -13
- data/app/assets/javascript/ahoy_captain/controllers/filter_modal_controller.js +0 -45
- data/app/assets/javascript/ahoy_captain/controllers/frame_link_controller.js +0 -20
- data/app/assets/javascript/ahoy_captain/controllers/interval_controller.js +0 -15
- data/app/assets/javascript/ahoy_captain/controllers/line_chart_controller.js +0 -251
- data/app/assets/javascript/ahoy_captain/controllers/map_controller.js +0 -47
- data/app/assets/javascript/ahoy_captain/controllers/predicate_select_controller.js +0 -10
- data/app/assets/javascript/ahoy_captain/controllers/properties_controller.js +0 -8
- data/app/assets/javascript/ahoy_captain/controllers/property_filter_controller.js +0 -45
- data/app/assets/javascript/ahoy_captain/controllers/tile_controller.js +0 -33
- data/app/assets/javascript/ahoy_captain/controllers/toggle_controller.js +0 -17
- data/app/assets/javascript/ahoy_captain/helpers/chart_utils.js +0 -156
- data/app/assets/javascript/ahoy_captain/helpers/countries.js +0 -2261
- data/app/assets/javascript/ahoy_captain/helpers/number_formatters.js +0 -55
- data/app/components/ahoy_captain/combobox_component.html.erb +0 -33
- data/app/components/ahoy_captain/combobox_component.rb +0 -13
- data/app/components/ahoy_captain/comparison_link_component.html.erb +0 -17
- data/app/components/ahoy_captain/comparison_link_component.rb +0 -44
- data/app/components/ahoy_captain/filter/dropdown_component.html.erb +0 -50
- data/app/components/ahoy_captain/filter/dropdown_component.rb +0 -51
- data/app/components/ahoy_captain/previous_next_component.html.erb +0 -8
- data/app/components/ahoy_captain/previous_next_component.rb +0 -11
- data/app/components/ahoy_captain/stats/comparable_container_component.html.erb +0 -25
- data/app/components/ahoy_captain/stats/comparable_container_component.rb +0 -86
- data/app/components/ahoy_captain/stats/container_component.html.erb +0 -15
- data/app/components/ahoy_captain/stats/container_component.rb +0 -26
- data/app/components/ahoy_captain/tables/devices_table_component.rb +0 -11
- data/app/components/ahoy_captain/tables/dynamic_table.rb +0 -13
- data/app/components/ahoy_captain/tables/dynamic_table_component.rb +0 -204
- data/app/components/ahoy_captain/tables/goals_table_component.rb +0 -17
- data/app/components/ahoy_captain/tables/header_component.html.erb +0 -5
- data/app/components/ahoy_captain/tables/header_component.rb +0 -18
- data/app/components/ahoy_captain/tables/headers/header_component.html.erb +0 -5
- data/app/components/ahoy_captain/tables/headers/header_component.rb +0 -16
- data/app/components/ahoy_captain/tables/properties_table_component.rb +0 -27
- data/app/components/ahoy_captain/tables/row_component.html.erb +0 -4
- data/app/components/ahoy_captain/tables/rows/row_component.html.erb +0 -6
- data/app/components/ahoy_captain/tables/rows/row_component.rb +0 -40
- data/app/controllers/ahoy_captain/exports_controller.rb +0 -14
- data/app/controllers/ahoy_captain/filters/goals_controller.rb +0 -9
- data/app/controllers/ahoy_captain/filters/properties/names_controller.rb +0 -11
- data/app/controllers/ahoy_captain/filters/properties/values_controller.rb +0 -15
- data/app/controllers/ahoy_captain/locations/cities_controller.rb +0 -22
- data/app/controllers/ahoy_captain/locations/countries_controller.rb +0 -22
- data/app/controllers/ahoy_captain/locations/maps_controller.rb +0 -24
- data/app/controllers/ahoy_captain/locations/regions_controller.rb +0 -22
- data/app/controllers/ahoy_captain/properties_controller.rb +0 -41
- data/app/models/ahoy_captain/comparison_mode.rb +0 -72
- data/app/models/ahoy_captain/export.rb +0 -48
- data/app/models/ahoy_captain/filter_parser.rb +0 -82
- data/app/models/ahoy_captain/range_from_params.rb +0 -78
- data/app/models/concerns/ahoy_captain/compare_mode.rb +0 -19
- data/app/models/concerns/ahoy_captain/limitable.rb +0 -17
- data/app/queries/ahoy_captain/campaign_query.rb +0 -14
- data/app/queries/ahoy_captain/city_query.rb +0 -11
- data/app/queries/ahoy_captain/country_query.rb +0 -10
- data/app/queries/ahoy_captain/device_query.rb +0 -10
- data/app/queries/ahoy_captain/region_query.rb +0 -11
- data/app/queries/ahoy_captain/source_query.rb +0 -10
- data/app/queries/ahoy_captain/stats/base_query.rb +0 -18
- data/app/queries/ahoy_captain/top_page_query.rb +0 -13
- data/app/queries/concerns/ahoy_captain/comparable_queries.rb +0 -25
- data/app/queries/concerns/ahoy_captain/comparable_query.rb +0 -138
- data/app/queries/concerns/ahoy_captain/lazy_comparable_query.rb +0 -42
- data/app/views/ahoy_captain/devices/_table.html.erb +0 -2
- data/app/views/ahoy_captain/layouts/shared/_tile_loader.html.erb +0 -12
- data/app/views/ahoy_captain/locations/maps/show.html.erb +0 -3
- data/app/views/ahoy_captain/properties/_form.html.erb +0 -6
- data/app/views/ahoy_captain/properties/index.html.erb +0 -3
- data/app/views/ahoy_captain/properties/show.html.erb +0 -6
- data/app/views/ahoy_captain/roots/_filters.html.erb +0 -80
- data/lib/ahoy_captain/filter_configuration/filter.rb +0 -16
- data/lib/ahoy_captain/filter_configuration/filter_collection.rb +0 -48
- data/lib/ahoy_captain/filters_configuration.rb +0 -77
- data/lib/ahoy_captain/predicate_label.rb +0 -7
- data/lib/generators/ahoy_captain/migration_generator.rb +0 -21
- data/lib/generators/ahoy_captain/templates/migration.rb.tt +0 -7
- /data/app/views/ahoy_captain/{locations/cities → cities}/index.html+details.erb +0 -0
- /data/app/views/ahoy_captain/{locations/cities → cities}/index.html.erb +0 -0
- /data/app/views/ahoy_captain/{locations/countries → countries}/index.html+details.erb +0 -0
- /data/app/views/ahoy_captain/{locations/countries → countries}/index.html.erb +0 -0
- /data/app/views/ahoy_captain/{locations/regions → regions}/index.html+details.erb +0 -0
- /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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bdbe217ea7df263239572663d05816b79847f65f7c4ec9dfa120749c0f5b45d3
|
|
4
|
+
data.tar.gz: 6176d55f8d2502a46891032ddfd51043fe4c4e7bd5efb9ac754d7e1576e4f866
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ec21ec3864bfb7b6d87fa65df0b1cb67879cd8cdd9b25c246bec2ccf9dac72d9858bfef19e9d0fb590dac11d5cb4dad491d81f5a81ca87532254051c2cb335b
|
|
7
|
+
data.tar.gz: 9a0a6fba58462a0aaea1ee6d5536e21c7ca5c40c4332c89688ae99409e94495ee7215b31aa948ee9123b10f821b9405742f3f4946ee5c50b96f156476f6ae80a
|
data/README.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
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.
|
|
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
|
-
###
|
|
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
|
-
*
|
|
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
|
-
|
|
68
|
+
Please and thank you in advance!
|
|
81
69
|
|
|
82
70
|
## License
|
|
83
71
|
|
data/Rakefile
CHANGED
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
require "bundler/setup"
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import "@hotwired/turbo-rails"
|
|
2
|
+
import "controllers"
|
|
3
|
+
import "chartkick"
|
|
4
|
+
import "Chart.bundle"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Application } from
|
|
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 {
|
|
1
|
+
import {Controller} from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 {
|
|
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 {
|
|
1
|
+
import {Controller} from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
static targets = [
|
|
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 {
|
|
1
|
+
import {Controller} from "@hotwired/stimulus"
|
|
2
2
|
import 'chartjs-plugin-datalabels';
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
113
|
-
const label = "Visitors"
|
|
114
|
-
let comparisonLabel = "Dropoff"
|
|
115
|
-
let comparisonValue = data.unique_visits;
|
|
136
|
+
const visitorsData = []
|
|
116
137
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
eagerLoadControllersFrom('controllers', application);
|
|
1
|
+
import {application} from "controllers/application"
|
|
2
|
+
import {eagerLoadControllersFrom} from "@hotwired/stimulus-loading"
|
|
3
|
+
eagerLoadControllersFrom("controllers", application)
|