ahoy_captain 0.8 → 0.9
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 +21 -13
- data/app/assets/javascript/ahoy_captain/application.js +4 -4
- data/app/assets/javascript/ahoy_captain/controllers/active_links_controller.js +14 -0
- data/app/assets/javascript/ahoy_captain/controllers/application.js +5 -5
- data/app/assets/javascript/ahoy_captain/controllers/application_controller.js +9 -10
- 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/item_controller.js +12 -0
- data/app/assets/javascript/ahoy_captain/controllers/filter_form_controller.js +13 -0
- data/app/assets/javascript/ahoy_captain/controllers/filter_tag_controller.js +11 -8
- data/app/assets/javascript/ahoy_captain/controllers/funnel_chart_controller.js +77 -107
- data/app/assets/javascript/ahoy_captain/controllers/index.js +4 -3
- data/app/assets/javascript/ahoy_captain/controllers/interval_controller.js +3 -3
- data/app/assets/javascript/ahoy_captain/controllers/line_chart_controller.js +37 -0
- data/app/assets/javascript/ahoy_captain/controllers/predicate_select_controller.js +10 -0
- data/app/assets/javascript/ahoy_captain/controllers/realtime_controller.js +9 -8
- data/app/assets/javascript/ahoy_captain/controllers/search_select_controller.js +65 -0
- data/app/assets/javascript/ahoy_captain/controllers/toggle_controller.js +17 -0
- data/app/components/ahoy_captain/dropdown_button_component.html.erb +5 -5
- data/app/components/ahoy_captain/dropdown_link_component.html.erb +5 -5
- data/app/components/ahoy_captain/filter/dropdown_component.html.erb +48 -0
- data/app/components/ahoy_captain/filter/dropdown_component.rb +51 -0
- data/app/components/ahoy_captain/filter/modal_component.html.erb +8 -7
- data/app/components/ahoy_captain/filter/select_component.html.erb +14 -12
- data/app/components/ahoy_captain/filter/select_component.rb +40 -9
- data/app/components/ahoy_captain/filter/tag_component.html.erb +8 -4
- data/app/components/ahoy_captain/filter/tag_component.rb +6 -30
- data/app/components/ahoy_captain/filter/tag_container_component.html.erb +2 -3
- data/app/components/ahoy_captain/filter/tag_container_component.rb +1 -8
- data/app/components/ahoy_captain/stats/container_component.html.erb +8 -0
- data/app/components/ahoy_captain/stats/container_component.rb +11 -0
- data/app/components/ahoy_captain/sticky_nav_component.html.erb +7 -19
- data/app/components/ahoy_captain/sticky_nav_component.rb +8 -0
- data/app/components/ahoy_captain/table_component.html.erb +4 -37
- data/app/components/ahoy_captain/table_component.rb +15 -4
- data/app/components/ahoy_captain/tables/headers/devices_header_component.html.erb +3 -0
- data/app/components/ahoy_captain/tables/headers/devices_header_component.rb +9 -0
- data/app/components/ahoy_captain/tables/headers/goals_header_component.html.erb +6 -0
- data/app/components/ahoy_captain/tables/headers/goals_header_component.rb +9 -0
- data/app/components/ahoy_captain/tables/headers/header_component.html.erb +5 -0
- data/app/components/ahoy_captain/tables/headers/header_component.rb +12 -0
- data/app/components/ahoy_captain/tables/rows/devices_row_component.html.erb +5 -0
- data/app/components/ahoy_captain/tables/rows/devices_row_component.rb +12 -0
- data/app/components/ahoy_captain/tables/rows/goals_row_component.html.erb +11 -0
- data/app/components/ahoy_captain/tables/rows/goals_row_component.rb +20 -0
- data/app/components/ahoy_captain/tables/rows/row_component.html.erb +6 -0
- data/app/components/ahoy_captain/tables/rows/row_component.rb +41 -0
- data/app/components/ahoy_captain/tile_component.html.erb +4 -3
- data/app/components/ahoy_captain/tile_component.rb +1 -1
- data/app/components/ahoy_captain/tooltip_component.html.erb +2 -2
- data/app/controllers/ahoy_captain/application_controller.rb +13 -14
- data/app/controllers/ahoy_captain/campaigns_controller.rb +2 -10
- data/app/controllers/ahoy_captain/cities_controller.rb +2 -6
- data/app/controllers/ahoy_captain/countries_controller.rb +2 -6
- data/app/controllers/ahoy_captain/devices_controller.rb +3 -6
- data/app/controllers/ahoy_captain/entry_pages_controller.rb +2 -4
- data/app/controllers/ahoy_captain/exit_pages_controller.rb +3 -4
- data/app/controllers/ahoy_captain/exports_controller.rb +15 -0
- data/app/controllers/ahoy_captain/filters/pages/entry_pages_controller.rb +2 -2
- data/app/controllers/ahoy_captain/filters/pages/exit_pages_controller.rb +1 -2
- data/app/controllers/ahoy_captain/filters/properties/names_controller.rb +11 -0
- data/app/controllers/ahoy_captain/filters/properties/values_controller.rb +15 -0
- data/app/controllers/ahoy_captain/filters/sources_controller.rb +1 -1
- data/app/controllers/ahoy_captain/regions_controller.rb +3 -7
- data/app/controllers/ahoy_captain/sources_controller.rb +2 -5
- data/app/controllers/ahoy_captain/stats/base_controller.rb +3 -3
- data/app/controllers/ahoy_captain/stats/bounce_rates_controller.rb +1 -0
- data/app/controllers/ahoy_captain/stats/total_pageviews_controller.rb +1 -0
- data/app/controllers/ahoy_captain/stats/total_visits_controller.rb +1 -0
- data/app/controllers/ahoy_captain/stats/unique_visitors_controller.rb +1 -0
- data/app/controllers/ahoy_captain/stats/views_per_visits_controller.rb +3 -2
- data/app/controllers/ahoy_captain/stats/visit_durations_controller.rb +1 -0
- data/app/controllers/ahoy_captain/top_pages_controller.rb +2 -8
- data/app/decorators/ahoy_captain/application_decorator.rb +27 -3
- data/app/decorators/ahoy_captain/campaign_decorator.rb +8 -0
- data/app/decorators/ahoy_captain/city_decorator.rb +12 -0
- data/app/decorators/ahoy_captain/country_decorator.rb +10 -0
- data/app/decorators/ahoy_captain/device_decorator.rb +13 -2
- data/app/decorators/ahoy_captain/page_decorator.rb +11 -0
- data/app/decorators/ahoy_captain/region_decorator.rb +16 -0
- data/app/decorators/ahoy_captain/source_decorator.rb +7 -0
- data/app/helpers/ahoy_captain/application_helper.rb +33 -0
- data/app/models/ahoy_captain/export.rb +48 -0
- data/app/models/ahoy_captain/filter_parser.rb +67 -0
- data/app/presenters/ahoy_captain/dashboard_presenter.rb +6 -1
- data/app/presenters/ahoy_captain/goals_presenter.rb +3 -2
- data/app/queries/ahoy_captain/application_query.rb +4 -3
- data/app/queries/ahoy_captain/campaign_query.rb +14 -0
- data/app/queries/ahoy_captain/city_query.rb +11 -0
- data/app/queries/ahoy_captain/country_query.rb +10 -0
- data/app/queries/ahoy_captain/device_query.rb +10 -0
- data/app/queries/ahoy_captain/entry_pages_query.rb +3 -2
- data/app/queries/ahoy_captain/event_query.rb +17 -15
- data/app/queries/ahoy_captain/exit_pages_query.rb +6 -4
- data/app/queries/ahoy_captain/region_query.rb +11 -0
- data/app/queries/ahoy_captain/source_query.rb +10 -0
- data/app/queries/ahoy_captain/top_page_query.rb +13 -0
- data/app/queries/ahoy_captain/visit_query.rb +1 -1
- data/app/views/ahoy_captain/devices/_table.html.erb +5 -0
- 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 +5 -2
- data/app/views/ahoy_captain/goals/index.html.erb +3 -35
- data/app/views/ahoy_captain/layouts/application.html.erb +3 -3
- data/app/views/ahoy_captain/realtimes/show.html.erb +1 -1
- data/app/views/ahoy_captain/roots/_filters.html.erb +34 -0
- data/app/views/ahoy_captain/roots/show.html.erb +33 -95
- data/app/views/ahoy_captain/stats/base/index.html.erb +4 -7
- data/app/views/ahoy_captain/stats/show.html.erb +6 -51
- data/config/routes.rb +7 -0
- data/lib/ahoy_captain/ahoy/event_methods.rb +36 -73
- data/lib/ahoy_captain/ahoy/visit_methods.rb +1 -1
- data/lib/ahoy_captain/configuration.rb +13 -3
- data/lib/ahoy_captain/engine.rb +18 -0
- data/lib/ahoy_captain/filter_configuration/filter.rb +16 -0
- data/lib/ahoy_captain/filter_configuration/filter_collection.rb +48 -0
- data/lib/ahoy_captain/filters_configuration.rb +73 -0
- data/lib/ahoy_captain/goals.rb +10 -2
- data/lib/ahoy_captain/period_collection.rb +1 -1
- data/lib/ahoy_captain/predicate_label.rb +7 -0
- data/lib/ahoy_captain/version.rb +1 -1
- data/lib/ahoy_captain.rb +1 -0
- data/lib/generators/ahoy_captain/templates/config.rb.tt +25 -0
- metadata +56 -20
- data/app/assets/javascript/ahoy_captain/controllers/filter_controller.js +0 -145
- data/app/assets/javascript/ahoy_captain/controllers/link_controller.js +0 -43
- data/app/assets/javascript/ahoy_captain/controllers/navigation_controller.js +0 -25
- data/app/models/ahoy_captain/current.rb +0 -9
- data/app/models/ahoy_captain/url_helpers.rb +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7334ddf143eb3d28e42766a6cc39f1acc5ee733679e5824399d668f349e1f68
|
|
4
|
+
data.tar.gz: 98735c61175fe289a4ffc73c6ac395c540c603d1a05ca95e1369eb5619081a99
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 737b08e21d4ea18f634ff5c19e10d0f9bc21f73eba746c93cc1f74f6459ebb9f8d95d7ca7ee82236bcb163cfbf6642acb30a547cc710c3d5e63abcaa4d49c1f6
|
|
7
|
+
data.tar.gz: 00dd0823f703ce906e201dd227b33a4cc0606f6d67680c63c1a1c6231110557e18ccfbb81a6964b601ba30a81fd8bd72a618b3da407f40c7f91181ed3cc6472a
|
data/README.md
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
# AhoyCaptain
|
|
1
|
+
# <img src="logo.png" style="max-height:100px" /> AhoyCaptain
|
|
2
2
|
|
|
3
|
-
<img src="logo.png" style="max-width:100px" />
|
|
4
3
|
|
|
5
4
|
A full-featured, mountable analytics dashboard for your Rails app, shamelessly inspired by Plausible Analytics, powered by the Ahoy gem.
|
|
6
5
|
|
|
7
|
-
<a href="https://github.com/joshmn/ahoy_captain/blob/main/ss.
|
|
6
|
+
<a href="https://github.com/joshmn/ahoy_captain/blob/main/ss.png"><img src="ss.png" style="max-width:300px" /></a>
|
|
8
7
|
## Notice
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## Some assumptions
|
|
13
|
-
|
|
14
|
-
Some hardcoded stuff as of writing; this will be more fully-featured in due time.
|
|
9
|
+
Currently requires using PG and a JSONB column for your data.
|
|
15
10
|
|
|
16
11
|
## Installation
|
|
17
12
|
|
|
@@ -29,11 +24,24 @@ $ bundle add ahoy_captain
|
|
|
29
24
|
$ rails g ahoy_captain:install
|
|
30
25
|
```
|
|
31
26
|
|
|
32
|
-
### 3.
|
|
27
|
+
### 3. Make sure your events are setup correctly
|
|
28
|
+
|
|
29
|
+
By default, AhoyCaptain assumes you're tracking `controller` and `action` in your `Ahoy::Event` properties, and a page view event is named `$view`.
|
|
30
|
+
|
|
31
|
+
For a quick sanity check:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
AhoyCaptain.event.where(name: AhoyCaptain.config.event[:view_name]).count
|
|
35
|
+
AhoyCaptain.event.with_routes.count
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
See the initializer `config/initializers/ahoy_captain.rb` for further customization.
|
|
39
|
+
|
|
40
|
+
### 4. Star this repo
|
|
33
41
|
|
|
34
42
|
No, seriously, I need all the internet clout I can get.
|
|
35
43
|
|
|
36
|
-
###
|
|
44
|
+
### 5. Analyze your nightmares
|
|
37
45
|
|
|
38
46
|
If you have a large dataset (> 1GB) you probably want some indexes. `rails g ahoy_captain:migration`
|
|
39
47
|
|
|
@@ -52,12 +60,12 @@ If you have a large dataset (> 1GB) you probably want some indexes. `rails g aho
|
|
|
52
60
|
* Device type
|
|
53
61
|
* OS
|
|
54
62
|
* UTM tags
|
|
63
|
+
* Goal
|
|
64
|
+
* CSV exports
|
|
55
65
|
|
|
56
66
|
## Coming soon ™️
|
|
57
67
|
|
|
58
68
|
* Date comparison
|
|
59
|
-
* More filters
|
|
60
|
-
* CSV exports
|
|
61
69
|
|
|
62
70
|
## Contributors
|
|
63
71
|
|
|
@@ -65,7 +73,7 @@ This was built during the Rails Hackathon in July 2023 with [afogel](https://git
|
|
|
65
73
|
|
|
66
74
|
## Contributions
|
|
67
75
|
|
|
68
|
-
|
|
76
|
+
Do your worst; please and thank you in advance! :)
|
|
69
77
|
|
|
70
78
|
## License
|
|
71
79
|
|
|
@@ -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';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["link"]
|
|
5
|
+
connect() {
|
|
6
|
+
this.handleLinkClick = (event) => {
|
|
7
|
+
this.linkTargets.forEach(link => link.classList.remove('text-primary', 'font-semibold'))
|
|
8
|
+
event.target.classList.add('text-primary', 'font-semibold')
|
|
9
|
+
}
|
|
10
|
+
this.linkTargets.forEach(link => {
|
|
11
|
+
link.addEventListener('click', this.handleLinkClick)
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -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,14 +1,13 @@
|
|
|
1
|
-
import {Controller} from
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
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
|
+
});
|
|
12
11
|
}
|
|
13
|
-
|
|
12
|
+
}
|
|
14
13
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {Controller} from
|
|
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
|
|
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,12 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Connects to data-controller="filter--item"
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = {
|
|
6
|
+
modal: String
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
openModal() {
|
|
10
|
+
document.getElementById(this.modalValue).showModal()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {Controller} from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
handleReset(event) {
|
|
5
|
+
event.preventDefault();
|
|
6
|
+
const openModal = document.querySelector('dialog.modal[open]');
|
|
7
|
+
openModal.querySelectorAll('input, select').forEach(element => {
|
|
8
|
+
element.value = ""
|
|
9
|
+
});
|
|
10
|
+
openModal.close()
|
|
11
|
+
this.element.requestSubmit()
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import {Controller} from
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
static targets = [
|
|
4
|
+
static targets = ['delete'];
|
|
5
|
+
|
|
5
6
|
static values = {
|
|
6
7
|
columnPredicate: String,
|
|
7
|
-
category: String
|
|
8
|
-
}
|
|
8
|
+
category: String,
|
|
9
|
+
};
|
|
9
10
|
|
|
10
11
|
remove(event) {
|
|
11
12
|
event.preventDefault();
|
|
12
|
-
this.dispatch('remove', {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
this.dispatch('remove', {
|
|
14
|
+
detail: {
|
|
15
|
+
paramKey: `q[${this.columnPredicateValue}]`,
|
|
16
|
+
paramValue: this.categoryValue,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
16
19
|
}
|
|
17
20
|
}
|
|
@@ -1,117 +1,87 @@
|
|
|
1
|
-
import {Controller} from
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
import 'chartjs-plugin-datalabels';
|
|
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
|
|
10
|
-
|
|
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
|
-
}
|
|
36
|
-
}
|
|
37
3
|
|
|
38
4
|
export default class extends Controller {
|
|
39
|
-
|
|
40
|
-
|
|
5
|
+
connect() {
|
|
6
|
+
const funnel = JSON.parse(this.element.dataset.data);
|
|
41
7
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
8
|
+
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"';
|
|
9
|
+
const labels = funnel.steps.map((step) => step.name);
|
|
10
|
+
const stepData = funnel.steps.map((step) => step.total_events);
|
|
11
|
+
const dropOffData = funnel.steps.map((step) => step.drop_off * 100);
|
|
46
12
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
13
|
+
const data = {
|
|
14
|
+
labels,
|
|
15
|
+
datasets: [
|
|
16
|
+
{
|
|
17
|
+
label: 'Visitors',
|
|
18
|
+
data: stepData,
|
|
19
|
+
borderRadius: 4,
|
|
20
|
+
stack: 'Stack 0',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: 'Dropoff',
|
|
24
|
+
data: dropOffData,
|
|
25
|
+
borderRadius: 4,
|
|
26
|
+
stack: 'Stack 0',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
64
30
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
31
|
+
const config = {
|
|
32
|
+
responsive: true,
|
|
33
|
+
maintainAspectRatio: false,
|
|
34
|
+
plugins: [ChartDataLabels],
|
|
35
|
+
type: 'bar',
|
|
36
|
+
data,
|
|
37
|
+
options: {
|
|
38
|
+
layout: {
|
|
39
|
+
padding: 100,
|
|
40
|
+
},
|
|
41
|
+
plugins: {
|
|
42
|
+
legend: {
|
|
43
|
+
display: false,
|
|
44
|
+
},
|
|
45
|
+
tooltip: {
|
|
46
|
+
mode: 'index',
|
|
47
|
+
intersect: true,
|
|
48
|
+
position: 'average',
|
|
49
|
+
},
|
|
50
|
+
datalabels: {
|
|
51
|
+
anchor: 'end',
|
|
52
|
+
align: 'end',
|
|
53
|
+
borderRadius: 4,
|
|
54
|
+
padding: {
|
|
55
|
+
top: 8, bottom: 8, right: 8, left: 8,
|
|
56
|
+
},
|
|
57
|
+
font: {
|
|
58
|
+
size: 12, weight: 'normal', lineHeight: 1.6, family: fontFamily,
|
|
59
|
+
},
|
|
60
|
+
textAlign: 'center',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
scales: {
|
|
64
|
+
y: { display: false },
|
|
65
|
+
x: {
|
|
66
|
+
position: 'bottom',
|
|
67
|
+
display: true,
|
|
68
|
+
border: { display: false },
|
|
69
|
+
grid: { drawBorder: false, display: false },
|
|
70
|
+
ticks: {
|
|
71
|
+
padding: 8,
|
|
72
|
+
font: { weight: 'bold', family: fontFamily, size: 14 },
|
|
73
|
+
color: 'rgb(228, 228, 231)',
|
|
107
74
|
},
|
|
108
|
-
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
109
79
|
|
|
110
|
-
|
|
80
|
+
const visitorsData = [];
|
|
111
81
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
82
|
+
new Chart(
|
|
83
|
+
this.element,
|
|
84
|
+
config,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
117
87
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import {application} from
|
|
2
|
-
import {eagerLoadControllersFrom} from
|
|
3
|
-
|
|
1
|
+
import { application } from 'controllers/application';
|
|
2
|
+
import { eagerLoadControllersFrom } from '@hotwired/stimulus-loading';
|
|
3
|
+
|
|
4
|
+
eagerLoadControllersFrom('controllers', application);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {Controller} from
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
4
|
handleChange(event) {
|
|
5
5
|
const url = new URL(event.target.form.action);
|
|
6
6
|
const interval = event.target.value;
|
|
7
|
-
url.searchParams.set(
|
|
8
|
-
event.target.closest('turbo-frame').src = url.href
|
|
7
|
+
url.searchParams.set('interval', interval);
|
|
8
|
+
event.target.closest('turbo-frame').src = url.href;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static values = {
|
|
5
|
+
data: Object,
|
|
6
|
+
label: String
|
|
7
|
+
}
|
|
8
|
+
connect() {
|
|
9
|
+
const getCSS = (varname) => {
|
|
10
|
+
return `hsl(${getComputedStyle(document.documentElement).getPropertyValue(varname)})`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
new Chart(this.element,
|
|
14
|
+
{
|
|
15
|
+
type: 'line',
|
|
16
|
+
data: {
|
|
17
|
+
labels: Object.keys(this.dataValue),
|
|
18
|
+
datasets: [
|
|
19
|
+
{
|
|
20
|
+
label: this.labelValue,
|
|
21
|
+
data: Object.values(this.dataValue),
|
|
22
|
+
borderColor: getCSS('--s'),
|
|
23
|
+
backgroundColor: getCSS('--sc'),
|
|
24
|
+
color: getCSS('--sf')
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
plugins: {
|
|
29
|
+
colors: {
|
|
30
|
+
forceOverride: true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {Controller} from
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
static targets = [
|
|
4
|
+
static targets = ['label'];
|
|
5
5
|
|
|
6
6
|
connect() {
|
|
7
|
-
this.reload = this.reload.bind(this)
|
|
8
|
-
this.setLabel = this.setLabel.bind(this)
|
|
7
|
+
this.reload = this.reload.bind(this);
|
|
8
|
+
this.setLabel = this.setLabel.bind(this);
|
|
9
9
|
this.labelCount = 0;
|
|
10
|
-
|
|
10
|
+
this.reloadInterval = setInterval(this.reload, 1000 * 30);
|
|
11
|
+
this.labelInterval = setInterval(this.setLabel, 1000);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
reload() {
|
|
@@ -17,11 +18,11 @@ export default class extends Controller {
|
|
|
17
18
|
|
|
18
19
|
setLabel() {
|
|
19
20
|
this.labelTarget.title = `Last updated ${this.labelCount} seconds ago`;
|
|
20
|
-
this.labelCount += 1
|
|
21
|
+
this.labelCount += 1;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
disconnect() {
|
|
24
|
-
clearInterval(this.labelInterval)
|
|
25
|
-
clearInterval(this.reloadInterval)
|
|
25
|
+
clearInterval(this.labelInterval);
|
|
26
|
+
clearInterval(this.reloadInterval);
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {Controller} from "@hotwired/stimulus"
|
|
2
|
+
import SlimSelect from 'slim-select'
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = {
|
|
6
|
+
query: String,
|
|
7
|
+
url: String,
|
|
8
|
+
selected: Array
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
connect() {
|
|
12
|
+
this.loadedInitialData = false;
|
|
13
|
+
this.search = this.search.bind(this)
|
|
14
|
+
this.select = new SlimSelect({
|
|
15
|
+
select: this.element,
|
|
16
|
+
data: [],
|
|
17
|
+
settings: {
|
|
18
|
+
contentPosition: 'relative',
|
|
19
|
+
contentLocation: this.element.closest('fieldset'),
|
|
20
|
+
searchText: 'Sorry, no results found',
|
|
21
|
+
searchPlaceholder: 'Type to populate results',
|
|
22
|
+
placeholderText: `Search`,
|
|
23
|
+
searchHighlight: true
|
|
24
|
+
},
|
|
25
|
+
events: {
|
|
26
|
+
beforeOpen: async () => {
|
|
27
|
+
if (!this.loadedInitialData) {
|
|
28
|
+
const data = await this.search("");
|
|
29
|
+
this.select.setData(data);
|
|
30
|
+
this.loadedInitialData = true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
search: this.search
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if(this.selectedValue.length) {
|
|
38
|
+
this.select.setData(this.selectedValue.map(item => ({ "text": item, "value": item })))
|
|
39
|
+
this.select.setSelected(this.selectedValue)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async search(query) {
|
|
44
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
45
|
+
const formData = new FormData(this.element.form);
|
|
46
|
+
|
|
47
|
+
let deleted = [];
|
|
48
|
+
for (const [key, value] of formData) {
|
|
49
|
+
if(!deleted.includes(key)) {
|
|
50
|
+
searchParams.delete(key)
|
|
51
|
+
deleted.push(key)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
searchParams.append(key, value)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
searchParams.delete(this.element.name);
|
|
58
|
+
searchParams.set(this.queryValue, query);
|
|
59
|
+
|
|
60
|
+
const response = await fetch(`${this.urlValue}?${searchParams.toString()}`);
|
|
61
|
+
const data = await response.json();
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
// Connects to data-controller="toggle"
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static targets = ['toggleable'];
|
|
6
|
+
static values = {
|
|
7
|
+
enable: Boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
trigger() {
|
|
11
|
+
if (this.enableValue) {
|
|
12
|
+
this.toggleableTargets.forEach(element => {
|
|
13
|
+
element.classList.toggle('hidden');
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<div class="dropdown dropdown-end">
|
|
2
|
-
<label
|
|
3
|
-
tabindex="0"
|
|
4
|
-
class="btn btn-ghost dark:hover:bg-neutral flex"
|
|
2
|
+
<label
|
|
3
|
+
tabindex="0"
|
|
4
|
+
class="btn btn-ghost dark:hover:bg-neutral flex"
|
|
5
5
|
>
|
|
6
6
|
<%= header_icon %>
|
|
7
7
|
<span><%= title %></span>
|
|
8
8
|
</label>
|
|
9
|
-
<ul class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box
|
|
9
|
+
<ul class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box">
|
|
10
10
|
<% options.each do |option| %>
|
|
11
11
|
<li>
|
|
12
12
|
<%= option %>
|
|
13
13
|
<li>
|
|
14
14
|
<% end %>
|
|
15
15
|
</ul>
|
|
16
|
-
</div>
|
|
16
|
+
</div>
|