ahoy_captain 0.83 → 0.91
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/controllers/active_links_controller.js +6 -3
- data/app/assets/javascript/ahoy_captain/controllers/filter/item_controller.js +12 -0
- data/app/assets/javascript/ahoy_captain/controllers/line_chart_controller.js +4 -4
- 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 +2 -2
- data/app/components/ahoy_captain/filter/select_component.html.erb +3 -3
- data/app/components/ahoy_captain/filter/select_component.rb +22 -8
- 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 +12 -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 +2 -2
- data/app/components/ahoy_captain/table_component.rb +3 -0
- data/app/components/ahoy_captain/tables/headers/goals_header_component.html.erb +1 -1
- data/app/components/ahoy_captain/tables/headers/header_component.html.erb +1 -1
- data/app/components/ahoy_captain/tables/rows/goals_row_component.html.erb +1 -1
- data/app/components/ahoy_captain/tables/rows/goals_row_component.rb +8 -0
- data/app/components/ahoy_captain/tables/rows/row_component.rb +2 -2
- 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/filters/sources_controller.rb +1 -1
- data/app/controllers/ahoy_captain/stats/base_controller.rb +3 -3
- data/app/helpers/ahoy_captain/application_helper.rb +33 -0
- data/app/models/ahoy_captain/filter_parser.rb +67 -0
- data/app/presenters/ahoy_captain/goals_presenter.rb +3 -2
- data/app/queries/ahoy_captain/stats/average_views_per_visit_query.rb +1 -1
- data/app/queries/ahoy_captain/stats/visit_duration_query.rb +1 -1
- data/app/views/ahoy_captain/funnels/show.html.erb +5 -2
- 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 +19 -89
- data/app/views/ahoy_captain/stats/base/index.html.erb +3 -3
- data/app/views/ahoy_captain/stats/show.html.erb +7 -52
- data/lib/ahoy_captain/ahoy/event_methods.rb +12 -3
- data/lib/ahoy_captain/ahoy/visit_methods.rb +1 -1
- data/lib/ahoy_captain/configuration.rb +16 -6
- data/lib/ahoy_captain/engine.rb +17 -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 +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 +14 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bdc2d607961f89d3b62c551c8aa157c962ab05e0fe7a61179c6c1c9de1c830d6
|
|
4
|
+
data.tar.gz: feef80dd54cb92203aab1468fa80c134e46e8e04f5bf2c6684a83d856ab05ec4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43b863dd93ad7a3139c2c73564b1edde7f1d4bbba9540466852cb5cce0f930c0eef8f9505eb513e98c1800a6a0c3f2f8563d20568b17a5b0c65c4e61ad52e433
|
|
7
|
+
data.tar.gz: 6078f2d0937c955073c150c10979ea9099be46ae2d2382f317369f429b5237d4d381303d28e1aa0e6a3b917e1010b170d5dfcf1bf331383889e6f7d5e699f260
|
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
|
|
|
@@ -2,14 +2,17 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
4
|
static targets = ["link"]
|
|
5
|
+
static values = {
|
|
6
|
+
classes: { type: Array, default: ["text-primary", "font-semibold"] }
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
connect() {
|
|
6
10
|
this.handleLinkClick = (event) => {
|
|
7
|
-
this.linkTargets.forEach(link => link.classList.remove(
|
|
8
|
-
event.target.classList.add(
|
|
11
|
+
this.linkTargets.forEach(link => this.classesValue.forEach(klass => link.classList.remove(klass)))
|
|
12
|
+
this.classesValue.forEach(klass => event.target.classList.add(klass))
|
|
9
13
|
}
|
|
10
14
|
this.linkTargets.forEach(link => {
|
|
11
15
|
link.addEventListener('click', this.handleLinkClick)
|
|
12
16
|
})
|
|
13
17
|
}
|
|
14
|
-
|
|
15
18
|
}
|
|
@@ -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
|
+
}
|
|
@@ -9,7 +9,6 @@ export default class extends Controller {
|
|
|
9
9
|
const getCSS = (varname) => {
|
|
10
10
|
return `hsl(${getComputedStyle(document.documentElement).getPropertyValue(varname)})`
|
|
11
11
|
}
|
|
12
|
-
Chart.register(Chart.Colors)
|
|
13
12
|
|
|
14
13
|
new Chart(this.element,
|
|
15
14
|
{
|
|
@@ -20,9 +19,9 @@ export default class extends Controller {
|
|
|
20
19
|
{
|
|
21
20
|
label: this.labelValue,
|
|
22
21
|
data: Object.values(this.dataValue),
|
|
23
|
-
borderColor: getCSS('--
|
|
24
|
-
backgroundColor: getCSS('--
|
|
25
|
-
color: getCSS('--
|
|
22
|
+
borderColor: getCSS('--s'),
|
|
23
|
+
backgroundColor: getCSS('--sc'),
|
|
24
|
+
color: getCSS('--sf')
|
|
26
25
|
}
|
|
27
26
|
]
|
|
28
27
|
},
|
|
@@ -33,5 +32,6 @@ export default class extends Controller {
|
|
|
33
32
|
}
|
|
34
33
|
},
|
|
35
34
|
)
|
|
35
|
+
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -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>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<div class="dropdown dropdown-end" data-controller='dropdown-label'>
|
|
2
|
-
<label
|
|
3
|
-
tabindex="0"
|
|
4
|
-
class="cursor-pointer flex <%= classes %>"
|
|
1
|
+
<div class="dropdown dropdown-end " data-controller='dropdown-label'>
|
|
2
|
+
<label
|
|
3
|
+
tabindex="0"
|
|
4
|
+
class="cursor-pointer flex <%= classes %>"
|
|
5
5
|
data-action='click->dropdown-label#removeHidden'
|
|
6
6
|
>
|
|
7
7
|
<span data-dropdown-label-target="label"><%= title %></span>
|
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
<li>
|
|
17
17
|
<% end %>
|
|
18
18
|
</ul>
|
|
19
|
-
</div>
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<div class="dropdown dropdown-end">
|
|
2
|
+
<label
|
|
3
|
+
tabindex="0"
|
|
4
|
+
class="btn btn-ghost dark:hover:bg-neutral flex"
|
|
5
|
+
>
|
|
6
|
+
<%= header_icon %>
|
|
7
|
+
<span><%= title %></span>
|
|
8
|
+
</label>
|
|
9
|
+
|
|
10
|
+
<ul class="w-72 dropdown-content z-[1] p-2 shadow bg-base-100 rounded-box" data-controller='toggle' data-toggle-enable-value="<%= advanced_filter_menu? %>">
|
|
11
|
+
<% if advanced_filter_menu? %>
|
|
12
|
+
<div id="advanced-filters" class="w-full text-sm leading-tight" >
|
|
13
|
+
<button class='w-full cursor-pointer block pl-4 pt-1 text-left hover:text-primary' data-action="click->toggle#trigger">+ Add filter</button>
|
|
14
|
+
<div class="divider my-1"></div>
|
|
15
|
+
<% filters.each do |_, filter| %>
|
|
16
|
+
<li class='flex flex-inline px-4 items-center' data-toggle-target='toggleable'>
|
|
17
|
+
<button title="Edit filter: <%= filter.title %>"
|
|
18
|
+
class="flex w-full justify-between link no-underline items-center group <% if filter.modal %>cursor-pointer<% else %>cursor-text<% end %> text-left"
|
|
19
|
+
onclick="<% if filter.modal %><%= filter.modal %>.showModal() <% end %>">
|
|
20
|
+
<span class="truncate w-48 ">
|
|
21
|
+
<%= filter.description %>
|
|
22
|
+
</span>
|
|
23
|
+
<% if filter.modal %>
|
|
24
|
+
<span class="group-hover:text-primary hover:text-primary ">
|
|
25
|
+
<%= edit_icon %>
|
|
26
|
+
</span>
|
|
27
|
+
<% end %>
|
|
28
|
+
</button>
|
|
29
|
+
<a title="Remove filter: <%= filter.title %>"
|
|
30
|
+
class="hover:text-primary link no-underline pl-2" href="<%= filter.url %>">
|
|
31
|
+
<%= remove_icon %>
|
|
32
|
+
</a>
|
|
33
|
+
<li>
|
|
34
|
+
<div class="divider my-1" data-toggle-target='toggleable'></div>
|
|
35
|
+
<% end %>
|
|
36
|
+
<li data-toggle-target='toggleable'>
|
|
37
|
+
<a class="block mx-auto pl-4 pb-1 " href="<%= AhoyCaptain::Engine.app.url_helpers.root_path %>">Clear all filters</a>
|
|
38
|
+
</li>
|
|
39
|
+
</div>
|
|
40
|
+
<% end %>
|
|
41
|
+
|
|
42
|
+
<div id="core-filters" class="menu <%= 'hidden' if advanced_filter_menu? %> pt-0" data-toggle-target='toggleable'>
|
|
43
|
+
<% AhoyCaptain.config.filters.each do |label, filter_group| %>
|
|
44
|
+
<li><button onClick="<%= filter_group.modal_name %>.showModal()" class='link no-underline' data-action="click->toggle#trigger"><%= label %></button></li>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
47
|
+
</ul>
|
|
48
|
+
</div>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AhoyCaptain::Filter::DropdownComponent < ViewComponent::Base
|
|
4
|
+
def initialize(filters:)
|
|
5
|
+
@filters = filters
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
attr_reader :filters
|
|
11
|
+
|
|
12
|
+
def header_icon
|
|
13
|
+
advanced_filter_menu? ? filters_icon : magnifier_icon
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def title
|
|
17
|
+
advanced_filter_menu? ? "#{filters.size} Filters" : 'Filter'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def advanced_filter_menu?
|
|
21
|
+
filter_categories.count >= ::AhoyCaptain::FilterParser::FILTER_MENU_MAX_SIZE
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def filter_categories
|
|
25
|
+
filters.values.map(&:values).flatten
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def magnifier_icon
|
|
29
|
+
%Q(
|
|
30
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="-ml-1 mr-1 h-4 w-4 md:h-4 md:w-4"><path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd"></path></svg>
|
|
31
|
+
).html_safe
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def filters_icon
|
|
35
|
+
%Q(
|
|
36
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="-ml-1 mr-1 h-4 w-4"><path d="M17 2.75a.75.75 0 00-1.5 0v5.5a.75.75 0 001.5 0v-5.5zM17 15.75a.75.75 0 00-1.5 0v1.5a.75.75 0 001.5 0v-1.5zM3.75 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5a.75.75 0 01.75-.75zM4.5 2.75a.75.75 0 00-1.5 0v5.5a.75.75 0 001.5 0v-5.5zM10 11a.75.75 0 01.75.75v5.5a.75.75 0 01-1.5 0v-5.5A.75.75 0 0110 11zM10.75 2.75a.75.75 0 00-1.5 0v1.5a.75.75 0 001.5 0v-1.5zM10 6a2 2 0 100 4 2 2 0 000-4zM3.75 10a2 2 0 100 4 2 2 0 000-4zM16.25 10a2 2 0 100 4 2 2 0 000-4z"></path></svg>
|
|
37
|
+
).html_safe
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def edit_icon
|
|
41
|
+
%Q(
|
|
42
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="w-4 h-4 ml-1 cursor-pointer"><path d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z"></path><path d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0010 3H4.75A2.75 2.75 0 002 5.75v9.5A2.75 2.75 0 004.75 18h9.5A2.75 2.75 0 0017 15.25V10a.75.75 0 00-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5z"></path></svg>
|
|
43
|
+
).html_safe
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def remove_icon
|
|
47
|
+
%Q(
|
|
48
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="w-4 h-4"><path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"></path></svg>
|
|
49
|
+
).html_safe
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
<fieldset>
|
|
4
4
|
<h5><%= title %></h5>
|
|
5
5
|
<%= modal_display %>
|
|
6
|
-
<button class="btn btn-primary mt-4" type="submit">Apply</button>
|
|
7
|
-
<button class="btn btn-primary mt-4" type="reset">Reset</button>
|
|
6
|
+
<button class="btn btn-active btn-primary mt-4" type="submit">Apply</button>
|
|
7
|
+
<button class="btn btn-active btn-primary mt-4" type="reset">Reset</button>
|
|
8
8
|
</fieldset>
|
|
9
9
|
</div>
|
|
10
10
|
<label class="modal-backdrop">
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
<div class="flex flex-col w-[20%]">
|
|
3
3
|
<label class="label"><%= label %></label>
|
|
4
4
|
<select class='select select-bordered' data-action='change->predicate-select#handleChange'>
|
|
5
|
-
<%
|
|
6
|
-
<option value="<%= option_value(predicate) %>" <%= 'selected' if selected_predicate
|
|
7
|
-
<%= predicate
|
|
5
|
+
<% @predicates.each do |predicate| %>
|
|
6
|
+
<option value="<%= option_value(predicate) %>" <%= 'selected' if selected_predicate?(predicate) %>>
|
|
7
|
+
<%= predicate_label(predicate) %>
|
|
8
8
|
</option>
|
|
9
9
|
<% end %>
|
|
10
10
|
</select>
|
|
@@ -12,27 +12,41 @@ class AhoyCaptain::Filter::SelectComponent < ViewComponent::Base
|
|
|
12
12
|
|
|
13
13
|
private
|
|
14
14
|
|
|
15
|
-
def selected_predicate
|
|
16
|
-
|
|
15
|
+
def selected_predicate?(predicate)
|
|
16
|
+
params.dig(:q, predicate_name(predicate)).present?
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def option_value(predicate)
|
|
20
|
-
name = "q[#{predicate}]"
|
|
20
|
+
name = "q[#{predicate_name(predicate)}]"
|
|
21
21
|
name += "[]" if multiple
|
|
22
22
|
name
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
def predicate_name(predicate)
|
|
26
|
+
"#{@column}_#{predicate}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def selected_predicate
|
|
30
|
+
@predicates.each do |predicate|
|
|
31
|
+
if params.dig(:q, predicate_name(predicate))
|
|
32
|
+
return predicate
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
25
39
|
def column_name_with_predicate
|
|
26
40
|
if selected_predicate
|
|
27
41
|
option_value(selected_predicate)
|
|
28
42
|
else
|
|
29
|
-
option_value(
|
|
43
|
+
option_value(@predicates.first)
|
|
30
44
|
end
|
|
31
45
|
end
|
|
32
46
|
|
|
33
47
|
def values
|
|
34
|
-
|
|
35
|
-
option = params.dig(:q, predicate)
|
|
48
|
+
@predicates.each do |predicate|
|
|
49
|
+
option = params.dig(:q, predicate_name(predicate))
|
|
36
50
|
if option
|
|
37
51
|
return option
|
|
38
52
|
end
|
|
@@ -41,8 +55,8 @@ class AhoyCaptain::Filter::SelectComponent < ViewComponent::Base
|
|
|
41
55
|
[]
|
|
42
56
|
end
|
|
43
57
|
|
|
44
|
-
def
|
|
45
|
-
|
|
58
|
+
def predicate_label(predicate)
|
|
59
|
+
AhoyCaptain::PredicateLabel[predicate]
|
|
46
60
|
end
|
|
47
61
|
|
|
48
62
|
attr_reader :label, :column, :url, :predicates, :form, :multiple
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
<div class="bg-base-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
<div class="bg-base-200 py-2 px-4 mx-2 flex items-center text-base-content" data-controller="filter--item" data-filter--item-modal-value="<%= tag_item.modal %>">
|
|
2
|
+
<span data-action='click->filter--item#openModal'>
|
|
3
|
+
<%= tag_item.column.titleize %> <%= tag_item.predicate %>
|
|
4
|
+
<span class="font-bold">
|
|
5
|
+
<%= tag_item.label %>
|
|
6
|
+
</span>
|
|
7
|
+
</span>
|
|
8
|
+
<a class="hover:text-primary" href="<%= tag_item.url %>">
|
|
5
9
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="w-4 h-4">
|
|
6
10
|
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"></path>
|
|
7
11
|
</svg>
|
|
@@ -1,38 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class AhoyCaptain::Filter::TagComponent < ViewComponent::Base
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
not_eq: 'not equals',
|
|
7
|
-
cont: 'contains',
|
|
8
|
-
in: 'in',
|
|
9
|
-
not_in: 'not in',
|
|
10
|
-
}
|
|
11
|
-
def initialize(column_predicate:, category:)
|
|
12
|
-
@column_predicate = column_predicate
|
|
13
|
-
@category = category
|
|
4
|
+
def initialize(tag_item:)
|
|
5
|
+
@tag_item = tag_item
|
|
14
6
|
end
|
|
15
7
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if search_params["q"][@column_predicate].is_a?(Array)
|
|
19
|
-
search_params["q"][@column_predicate] = search_params["q"][@column_predicate] - [@category]
|
|
20
|
-
else
|
|
21
|
-
search_params["q"].delete(@column_predicate)
|
|
22
|
-
end
|
|
8
|
+
private
|
|
9
|
+
attr_reader :tag_item
|
|
23
10
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
attr_reader :column_predicate, :category
|
|
29
|
-
|
|
30
|
-
def column_with_predicate
|
|
31
|
-
PREDICATES.keys.map(&:to_s).each do |predicate|
|
|
32
|
-
if column_predicate.include?(predicate)
|
|
33
|
-
before, match, after = column_predicate.partition(predicate)
|
|
34
|
-
return [before.gsub('_',' '), PREDICATES[match.to_sym]].join(' ').humanize
|
|
35
|
-
end
|
|
36
|
-
end
|
|
11
|
+
def modal
|
|
12
|
+
tag_item.modal
|
|
37
13
|
end
|
|
38
14
|
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
<%
|
|
2
|
-
|
|
3
|
-
<%= render AhoyCaptain::Filter::TagComponent.new(column_predicate: column_predicate, category: category) %>
|
|
1
|
+
<% ::AhoyCaptain::FilterParser.parse(request).each do |_, filter| %>
|
|
2
|
+
<%= render AhoyCaptain::Filter::TagComponent.new(tag_item: filter) %>
|
|
4
3
|
<% end %>
|
|
5
4
|
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class AhoyCaptain::Filter::TagContainerComponent < ViewComponent::Base
|
|
4
|
-
def initialize
|
|
5
|
-
@filters = filters.to_unsafe_h.to_a.map do |key, values|
|
|
6
|
-
Array(values).map { |value| [key, value] }
|
|
7
|
-
end.flatten(1).reject { |k,v| v.blank? }
|
|
8
|
-
|
|
4
|
+
def initialize
|
|
9
5
|
end
|
|
10
|
-
|
|
11
|
-
private
|
|
12
|
-
attr_reader :filters
|
|
13
6
|
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<a href="<%= @url %>" class="hover:text-primary text-base-content link no-underline" data-turbo-frame="chart">
|
|
2
|
+
<dt class="font-normal <%= 'text-primary' if @selected %>" data-active-links-target="link"><%= @label %></dt>
|
|
3
|
+
<dd class="mt-1 flex items-baseline justify-between md:block lg:flex">
|
|
4
|
+
<div class="flex items-baseline text-2xl font-semibold">
|
|
5
|
+
<%= @value %>
|
|
6
|
+
</div>
|
|
7
|
+
</dd>
|
|
8
|
+
</a>
|
|
@@ -1,37 +1,25 @@
|
|
|
1
|
-
<div class="max-w-6xl flex justify-between sticky top-0 min-h-sm z-[99999] bg-base-
|
|
1
|
+
<div class="max-w-6xl flex justify-between sticky top-0 min-h-sm z-[99999] py-4 bg-base-100">
|
|
2
2
|
<div class="flex items-center">
|
|
3
3
|
<a href="/">
|
|
4
4
|
<img src="<%= image_path "ahoy_captain/logo.png" %>" alt="AhoyCaptainLogo" class='w-16 h-16 rounded-full'>
|
|
5
5
|
</a>
|
|
6
|
-
<% if
|
|
7
|
-
<%= render AhoyCaptain::Filter::TagContainerComponent.new
|
|
6
|
+
<% if tag_list_hidden? %>
|
|
7
|
+
<%= render AhoyCaptain::Filter::TagContainerComponent.new %>
|
|
8
8
|
<% else %>
|
|
9
9
|
<%= realtime_update %>
|
|
10
10
|
<% end %>
|
|
11
11
|
</div>
|
|
12
12
|
<div class="flex flex-row-reverse col-span-2 items-center">
|
|
13
|
-
<%= render AhoyCaptain::DropdownLinkComponent.new(title: params[:start_date] ? "Custom Range" : (AhoyCaptain.config.ranges.find(params[:period]).try(:label) || "Period"), classes: 'btn btn-base-100 no-underline
|
|
13
|
+
<%= render AhoyCaptain::DropdownLinkComponent.new(title: params[:start_date] ? "Custom Range" : (AhoyCaptain.config.ranges.find(params[:period]).try(:label) || "Period"), classes: 'btn btn-base-100 no-underline hover:bg-base-100') do |dropdown| %>
|
|
14
14
|
<% dropdown.with_option do %>
|
|
15
15
|
<% AhoyCaptain.config.ranges.each do |param, range| %>
|
|
16
16
|
<a class='link no-underline' href="<%= request.path %>?<%= request.query_parameters.except("start_date", "end_date").merge("period" => param).to_query %>"><%= range.label %></a>
|
|
17
17
|
<% end %>
|
|
18
18
|
|
|
19
|
-
<a class='link no-underline' href='#' onclick="event.preventDefault(); customRangeModal.showModal()">Custom range</a>
|
|
20
|
-
<% end %>
|
|
21
|
-
<% end %>
|
|
22
|
-
<%= render AhoyCaptain::DropdownButtonComponent.new(title: 'Filter') do |dropdown| %>
|
|
23
|
-
<% dropdown.with_header_icon do %>
|
|
24
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="-ml-1 mr-1 h-4 w-4 md:h-4 md:w-4">
|
|
25
|
-
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd"></path>
|
|
26
|
-
</svg>
|
|
27
|
-
<% end %>
|
|
28
|
-
<% dropdown.with_option do %>
|
|
29
|
-
<button onClick="pageModal.showModal()" class='link no-underline'>Page</button>
|
|
30
|
-
<button onClick="countryModal.showModal()" class='link no-underline'>Geography</button>
|
|
31
|
-
<button onClick="screenModal.showModal()" class='link no-underline'>Screen</button>
|
|
32
|
-
<button onClick="osModal.showModal()" class='link no-underline'>Operating System</button>
|
|
33
|
-
<button onClick="utmModal.showModal()" class='link no-underline'>UTM Tags</button>
|
|
19
|
+
<a class='link no-underline ' href='#' onclick="event.preventDefault(); customRangeModal.showModal()">Custom range</a>
|
|
34
20
|
<% end %>
|
|
35
21
|
<% end %>
|
|
22
|
+
|
|
23
|
+
<%= render AhoyCaptain::Filter::DropdownComponent.new(filters: filters) %>
|
|
36
24
|
</div>
|
|
37
25
|
</div>
|
|
@@ -2,4 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
class AhoyCaptain::StickyNavComponent < ViewComponent::Base
|
|
4
4
|
renders_one :realtime_update
|
|
5
|
+
|
|
6
|
+
def filters
|
|
7
|
+
@filters ||= ::AhoyCaptain::FilterParser.parse(request)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def tag_list_hidden?
|
|
11
|
+
filters.values.map(&:values).flatten.size < ::AhoyCaptain::FilterParser::FILTER_MENU_MAX_SIZE
|
|
12
|
+
end
|
|
5
13
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<div class="flex flex-col min-h-[380px] w-full pt-4">
|
|
1
|
+
<div class="flex flex-col <%= 'min-h-[380px]' if fixed_height? %> w-full pt-4">
|
|
2
2
|
<%= render @header %>
|
|
3
|
-
<div class='min-h-[420px]'>
|
|
3
|
+
<div class='<%= 'min-h-[420px]' if fixed_height? %>'>
|
|
4
4
|
<div class="grow">
|
|
5
5
|
<% if items.respond_to?(:each) && items.any? %>
|
|
6
6
|
<% items.each do |item| %>
|
|
@@ -2,6 +2,14 @@ module AhoyCaptain
|
|
|
2
2
|
module Tables
|
|
3
3
|
module Rows
|
|
4
4
|
class GoalsRowComponent < RowComponent
|
|
5
|
+
def search_params
|
|
6
|
+
view_context.search_params
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def url
|
|
10
|
+
query = search_params.dup.merge(q: { goal_in: @item.goal_id}).to_query
|
|
11
|
+
AhoyCaptain::Engine.app.url_helpers.root_path + "?#{query}"
|
|
12
|
+
end
|
|
5
13
|
|
|
6
14
|
def total
|
|
7
15
|
@item.total_count
|
|
@@ -10,7 +10,7 @@ module AhoyCaptain
|
|
|
10
10
|
def progress_bar(value, max, label)
|
|
11
11
|
items = []
|
|
12
12
|
items << view_context.content_tag(:progress, "", class: "progress-primary bg-base-100 h-8 grow", value: value, max: max)
|
|
13
|
-
items << view_context.content_tag(:span, class: "grow text-elipsis overflow-hidden absolute left-4 bottom-3 h-8 text-
|
|
13
|
+
items << view_context.content_tag(:span, class: "grow text-elipsis overflow-hidden absolute left-4 bottom-3 h-8 text-primary-content") do
|
|
14
14
|
label
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ module AhoyCaptain
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def item(value = nil, &block)
|
|
21
|
-
view_context.content_tag(:span, class: "w-8 ml-8 text-right") do
|
|
21
|
+
view_context.content_tag(:span, class: "w-8 ml-8 text-right ") do
|
|
22
22
|
if value
|
|
23
23
|
value
|
|
24
24
|
else
|