rails_local_analytics 0.1.0 → 0.2.1
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 +9 -4
- data/app/assets/stylesheets/rails_local_analytics/application.css +14 -2
- data/app/controllers/rails_local_analytics/application_controller.rb +4 -0
- data/app/controllers/rails_local_analytics/dashboard_controller.rb +109 -15
- data/app/jobs/rails_local_analytics/record_request_job.rb +24 -20
- data/app/models/rails_local_analytics/application_record.rb +1 -5
- data/app/views/layouts/rails_local_analytics/application.html.erb +10 -5
- data/app/views/rails_local_analytics/dashboard/index.html.erb +141 -62
- data/config/initializers/browser_monkey_patches.rb +19 -0
- data/config/routes.rb +3 -2
- data/lib/rails_local_analytics/version.rb +1 -1
- metadata +3 -4
- data/app/assets/javascripts/rails_local_analytics/application.js +0 -7
- data/app/lib/rails_local_analytics/histogram.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbb7ca6534efc3666a302a04271092906ec9b2fc65a316d82b7811e2dd962668
|
4
|
+
data.tar.gz: 2cc17d06cf61d6b4b7fd2c559079965fa6c250b12d901b05a3317389e2d5b69d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d9e8dea022bb79486a8bc6395106841a4f9d056accf68f3534072bac1e205f40df426664ed071abd54715d38b0a00ef879aaa080b4a0a51f6ba3e31b207101e
|
7
|
+
data.tar.gz: e249aa1fb3f83ccaf75ffdb774192de6549889b8fb38b3537f5a99cc5edc6aab7da7d1b01caccb6012ffa4c4695739f2a15107ecbc49804db9e3e61c856ea13f
|
data/README.md
CHANGED
@@ -27,6 +27,8 @@ It is fully customizable to store more details if desired.
|
|
27
27
|
|
28
28
|

|
29
29
|
|
30
|
+

|
31
|
+
|
30
32
|
## Installation
|
31
33
|
|
32
34
|
```ruby
|
@@ -118,8 +120,8 @@ class ApplicationController < ActionController::Base
|
|
118
120
|
RailsLocalAnalytics.record_request(
|
119
121
|
request: request,
|
120
122
|
custom_attributes: { # optional
|
121
|
-
|
122
|
-
|
123
|
+
site: site_based_attrs,
|
124
|
+
page: {},
|
123
125
|
},
|
124
126
|
)
|
125
127
|
end
|
@@ -136,6 +138,9 @@ Some examples of additional things you may want to track:
|
|
136
138
|
* You may not need to store this in a new column, one example pattern could be to store this data in the existing `platform` database field
|
137
139
|
- Country detection
|
138
140
|
* Country detection is difficult. As such we dont try to include it by default.
|
141
|
+
* Consider using language detection instead
|
142
|
+
- Language detection
|
143
|
+
* You can gather the language from the `request.env["HTTP_ACCEPT_LANGUAGE"]` or `browser.accept_language.first.full`
|
139
144
|
- Users or organizations
|
140
145
|
* You may want to track your users or another model which is a core tenant to your particular application
|
141
146
|
|
@@ -183,8 +188,8 @@ class ApplicationController
|
|
183
188
|
def record_page_view
|
184
189
|
# perform other logic and call RailsLocalAnalytics.record_request
|
185
190
|
|
186
|
-
TrackedRequestsByDayPage.where("
|
187
|
-
TrackedRequestsByDaySite.where("
|
191
|
+
TrackedRequestsByDayPage.where("day < ?", 3.months.ago).delete_all
|
192
|
+
TrackedRequestsByDaySite.where("day < ?", 3.months.ago).delete_all
|
188
193
|
end
|
189
194
|
end
|
190
195
|
```
|
@@ -1,3 +1,11 @@
|
|
1
|
+
body{
|
2
|
+
padding-bottom: 20px;
|
3
|
+
}
|
4
|
+
|
5
|
+
table td{
|
6
|
+
overflow-wrap: anywhere;
|
7
|
+
}
|
8
|
+
|
1
9
|
input, select {
|
2
10
|
border-radius: 5px;
|
3
11
|
border: 1px solid #333;
|
@@ -5,6 +13,10 @@ input, select {
|
|
5
13
|
padding: 2px 5px;
|
6
14
|
}
|
7
15
|
|
8
|
-
.
|
9
|
-
|
16
|
+
button.is-link{
|
17
|
+
border: none;
|
18
|
+
background: none;
|
19
|
+
color: #2780e3;
|
20
|
+
text-decoration: underline;
|
21
|
+
cursor: pointer;
|
10
22
|
}
|
@@ -1,22 +1,24 @@
|
|
1
1
|
module RailsLocalAnalytics
|
2
2
|
class DashboardController < ApplicationController
|
3
|
+
PER_PAGE_LIMIT = 1000
|
4
|
+
|
5
|
+
helper_method :pagination_page_number
|
3
6
|
|
4
7
|
def index
|
5
|
-
params[:type] ||= "
|
8
|
+
params[:type] ||= "page"
|
6
9
|
|
7
10
|
case params[:type]
|
8
|
-
when "
|
11
|
+
when "site"
|
9
12
|
@klass = TrackedRequestsByDaySite
|
10
|
-
when "
|
13
|
+
when "page"
|
11
14
|
@klass = TrackedRequestsByDayPage
|
12
15
|
else
|
13
|
-
|
16
|
+
head 404
|
17
|
+
return
|
14
18
|
end
|
15
19
|
|
16
20
|
if params[:group_by].present? && !@klass.display_columns.include?(params[:group_by])
|
17
|
-
|
18
|
-
else
|
19
|
-
params[:group_by] ||= "All"
|
21
|
+
raise ArgumentError
|
20
22
|
end
|
21
23
|
|
22
24
|
if params[:start_date].present?
|
@@ -35,27 +37,119 @@ module RailsLocalAnalytics
|
|
35
37
|
@end_date = @start_date
|
36
38
|
end
|
37
39
|
|
38
|
-
@
|
40
|
+
@results = fetch_records(@start_date, @end_date)
|
41
|
+
|
42
|
+
if @results.size < PER_PAGE_LIMIT
|
43
|
+
prev_start_date, prev_end_date = get_prev_dates(@start_date, @end_date)
|
44
|
+
|
45
|
+
@prev_period_results = fetch_records(prev_start_date, prev_end_date)
|
46
|
+
|
47
|
+
if @prev_period_results.size >= PER_PAGE_LIMIT
|
48
|
+
@prev_period_results = nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def difference
|
54
|
+
case params.require(:type)
|
55
|
+
when "site"
|
56
|
+
@klass = TrackedRequestsByDaySite
|
57
|
+
when "page"
|
58
|
+
@klass = TrackedRequestsByDayPage
|
59
|
+
end
|
60
|
+
|
61
|
+
start_date = Date.parse(params.require(:start_date))
|
62
|
+
end_date = Date.parse(params.require(:end_date))
|
63
|
+
|
64
|
+
prev_start_date, prev_end_date = get_prev_dates(start_date, end_date)
|
65
|
+
|
66
|
+
where_conditions = params.require(:conditions).permit(*@klass.display_columns)
|
67
|
+
|
68
|
+
current_total = fetch_records(
|
69
|
+
start_date,
|
70
|
+
end_date,
|
71
|
+
where_conditions: where_conditions,
|
72
|
+
pluck_columns: ["SUM(total)"],
|
73
|
+
).first
|
74
|
+
|
75
|
+
prev_total = fetch_records(
|
76
|
+
prev_start_date,
|
77
|
+
prev_end_date,
|
78
|
+
where_conditions: where_conditions,
|
79
|
+
pluck_columns: ["SUM(total)"],
|
80
|
+
).first
|
39
81
|
|
40
|
-
|
41
|
-
|
82
|
+
if prev_total
|
83
|
+
diff = current_total - prev_total
|
84
|
+
else
|
85
|
+
diff = current_total
|
86
|
+
end
|
42
87
|
|
43
|
-
|
88
|
+
render json: {difference: diff}
|
44
89
|
end
|
45
90
|
|
46
91
|
private
|
47
92
|
|
48
|
-
def fetch_records(start_date, end_date)
|
93
|
+
def fetch_records(start_date, end_date, where_conditions: nil, pluck_columns: nil)
|
49
94
|
tracked_requests = @klass
|
50
|
-
.where("day >= ?",
|
51
|
-
.where("day <= ?",
|
95
|
+
.where("day >= ?", start_date)
|
96
|
+
.where("day <= ?", end_date)
|
52
97
|
.order(total: :desc)
|
53
98
|
|
99
|
+
if where_conditions.nil? && pluck_columns.nil?
|
100
|
+
tracked_requests = tracked_requests
|
101
|
+
.limit(PER_PAGE_LIMIT)
|
102
|
+
.offset(PER_PAGE_LIMIT * (pagination_page_number-1))
|
103
|
+
|
104
|
+
if params[:filter].present?
|
105
|
+
col, val = params[:filter].split("==")
|
106
|
+
|
107
|
+
if @klass.display_columns.include?(col)
|
108
|
+
tracked_requests = tracked_requests.where(col => val)
|
109
|
+
else
|
110
|
+
raise ArgumentError
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if where_conditions
|
116
|
+
tracked_requests = tracked_requests.where(where_conditions)
|
117
|
+
end
|
118
|
+
|
54
119
|
if params[:search].present?
|
55
120
|
tracked_requests = tracked_requests.multi_search(params[:search])
|
56
121
|
end
|
57
122
|
|
58
|
-
|
123
|
+
if params[:group_by].present?
|
124
|
+
group_by_columns = [params[:group_by]]
|
125
|
+
pluck_columns = [params[:group_by], "SUM(total)"]
|
126
|
+
else
|
127
|
+
group_by_columns = @klass.display_columns
|
128
|
+
pluck_columns ||= @klass.display_columns + ["SUM(total)"]
|
129
|
+
end
|
130
|
+
|
131
|
+
tracked_requests
|
132
|
+
.group(*group_by_columns)
|
133
|
+
.pluck(*pluck_columns)
|
59
134
|
end
|
135
|
+
|
136
|
+
def pagination_page_number
|
137
|
+
page = params[:page].presence.to_i || 1
|
138
|
+
page = 1 if page.zero?
|
139
|
+
page
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_prev_dates(start_date, end_date)
|
143
|
+
if start_date == end_date
|
144
|
+
prev_start_date = start_date - 1.day
|
145
|
+
prev_end_date = prev_start_date
|
146
|
+
else
|
147
|
+
duration = end_date - start_date
|
148
|
+
prev_start_date = start_date - duration
|
149
|
+
prev_end_date = end_date - duration
|
150
|
+
end
|
151
|
+
return [prev_start_date, prev_end_date]
|
152
|
+
end
|
153
|
+
|
60
154
|
end
|
61
155
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module RailsLocalAnalytics
|
2
2
|
class RecordRequestJob < ApplicationJob
|
3
|
-
MODELS = [TrackedRequestsByDaySite, TrackedRequestsByDayPage].freeze
|
4
3
|
def perform(json)
|
5
4
|
if json.is_a?(String)
|
6
5
|
json = JSON.parse(json)
|
@@ -8,49 +7,56 @@ module RailsLocalAnalytics
|
|
8
7
|
|
9
8
|
request_hash = json.fetch("request_hash")
|
10
9
|
|
11
|
-
|
10
|
+
custom_attributes_by_type = json.fetch("custom_attributes")
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
["site", "page"].each do |type|
|
13
|
+
case type
|
14
|
+
when "site"
|
15
|
+
klass = TrackedRequestsByDaySite
|
16
|
+
when "page"
|
17
|
+
klass = TrackedRequestsByDayPage
|
18
|
+
end
|
19
|
+
|
20
|
+
custom_attrs = custom_attributes_by_type && custom_attributes_by_type[type]
|
15
21
|
|
16
|
-
attrs = build_attrs(
|
22
|
+
attrs = build_attrs(klass, custom_attrs, request_hash)
|
17
23
|
|
18
24
|
attrs["day"] = json.fetch("day")
|
19
25
|
|
20
|
-
existing_record =
|
26
|
+
existing_record = klass.find_by(attrs)
|
21
27
|
|
22
28
|
if existing_record
|
23
29
|
existing_record.increment!(:total, 1)
|
24
30
|
else
|
25
|
-
|
31
|
+
klass.create!(attrs)
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
29
35
|
|
30
36
|
private
|
31
37
|
|
32
|
-
def build_attrs(
|
38
|
+
def build_attrs(klass, attrs, request_hash)
|
33
39
|
attrs ||= {}
|
34
40
|
|
35
41
|
field = "url_hostname"
|
36
|
-
if !skip_field?(field, attrs,
|
37
|
-
attrs[field] = request_hash.fetch("host")
|
42
|
+
if !skip_field?(field, attrs, klass)
|
43
|
+
attrs[field] = request_hash.fetch("host")
|
38
44
|
end
|
39
45
|
|
40
46
|
field = "url_path"
|
41
|
-
if !skip_field?(field, attrs,
|
42
|
-
attrs[field] = request_hash.fetch("path")
|
47
|
+
if !skip_field?(field, attrs, klass)
|
48
|
+
attrs[field] = request_hash.fetch("path")
|
43
49
|
end
|
44
50
|
|
45
51
|
if request_hash.fetch("referrer").present?
|
46
52
|
field = "referrer_hostname"
|
47
|
-
if !skip_field?(field,attrs,
|
53
|
+
if !skip_field?(field,attrs, klass)
|
48
54
|
referrer_hostname, referrer_path = split_referrer(request_hash.fetch("referrer"))
|
49
55
|
attrs[field] = referrer_hostname
|
50
56
|
end
|
51
57
|
|
52
58
|
field = "referrer_path"
|
53
|
-
if !skip_field?(field, attrs,
|
59
|
+
if !skip_field?(field, attrs, klass)
|
54
60
|
if referrer_path.nil?
|
55
61
|
referrer_hostname, referrer_path = split_referrer(request_hash.fetch("referrer"))
|
56
62
|
end
|
@@ -60,13 +66,13 @@ module RailsLocalAnalytics
|
|
60
66
|
|
61
67
|
if request_hash.fetch("user_agent").present?
|
62
68
|
field = "platform"
|
63
|
-
if !skip_field?(field, attrs,
|
69
|
+
if !skip_field?(field, attrs, klass)
|
64
70
|
browser ||= create_browser_object(request_hash)
|
65
71
|
attrs[field] = browser.platform.name
|
66
72
|
end
|
67
73
|
|
68
74
|
field = "browser_engine"
|
69
|
-
if !skip_field?(field, attrs,
|
75
|
+
if !skip_field?(field, attrs, klass)
|
70
76
|
browser ||= create_browser_object(request_hash)
|
71
77
|
attrs[field] = get_browser_engine(browser)
|
72
78
|
end
|
@@ -76,8 +82,6 @@ module RailsLocalAnalytics
|
|
76
82
|
end
|
77
83
|
|
78
84
|
def split_referrer(referrer)
|
79
|
-
referrer = referrer.downcase
|
80
|
-
|
81
85
|
uri = URI(referrer)
|
82
86
|
|
83
87
|
if uri.host.present?
|
@@ -114,8 +118,8 @@ module RailsLocalAnalytics
|
|
114
118
|
)
|
115
119
|
end
|
116
120
|
|
117
|
-
def skip_field?(field, attrs,
|
118
|
-
attrs&.has_key?(field) || !
|
121
|
+
def skip_field?(field, attrs, klass)
|
122
|
+
attrs&.has_key?(field) || !klass.column_names.include?(field)
|
119
123
|
end
|
120
124
|
|
121
125
|
end
|
@@ -15,7 +15,7 @@ module RailsLocalAnalytics
|
|
15
15
|
sql_conditions << "(#{col} #{like} :search)"
|
16
16
|
end
|
17
17
|
|
18
|
-
relation =
|
18
|
+
relation = relation.where(sql_conditions.join(" OR "), search: "%#{sanitize_sql_like(str)}%")
|
19
19
|
end
|
20
20
|
|
21
21
|
next relation
|
@@ -26,9 +26,5 @@ module RailsLocalAnalytics
|
|
26
26
|
column_names - ["id", "created_at", "updated_at", "total", "day"]
|
27
27
|
end
|
28
28
|
|
29
|
-
def matches?(other_record)
|
30
|
-
day == other_record.day && self.class.display_columns.all?{|col_name| self.send(col_name) == other_record.send(col_name) }
|
31
|
-
end
|
32
|
-
|
33
29
|
end
|
34
30
|
end
|
@@ -9,12 +9,7 @@
|
|
9
9
|
<%= csp_meta_tag %>
|
10
10
|
|
11
11
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.4.0/cosmo/bootstrap.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
12
|
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.32.0/css/theme.default.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
13
12
|
<%= stylesheet_link_tag "rails_local_analytics/application" %>
|
14
|
-
|
15
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
16
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.0/js/jquery.tablesorter.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
17
|
-
<%= javascript_include_tag "rails_local_analytics/application" %>
|
18
13
|
</head>
|
19
14
|
|
20
15
|
<body>
|
@@ -23,6 +18,16 @@
|
|
23
18
|
<div class="navbar-header">
|
24
19
|
<%= link_to title , root_path, class: "navbar-brand" %>
|
25
20
|
</h1>
|
21
|
+
|
22
|
+
<ul class="nav navbar-nav navbar-right">
|
23
|
+
<li class="<%= 'active' if params[:type] == "page" %>">
|
24
|
+
<%= link_to "Requests By Page", tracked_requests_path(type: "page") %>
|
25
|
+
</li>
|
26
|
+
|
27
|
+
<li class="<%= 'active' if params[:type] == "site" %>">
|
28
|
+
<%= link_to "Requests By Site", tracked_requests_path(type: "site") %>
|
29
|
+
</li>
|
30
|
+
</ul>
|
26
31
|
</div>
|
27
32
|
</nav>
|
28
33
|
|
@@ -1,81 +1,117 @@
|
|
1
|
-
<%
|
2
|
-
<% site_group_by_options = options_for_select([["All", "All"]] + TrackedRequestsByDaySite.display_columns.map{|x| [x.titleize.sub("Url ", "URL "), x]}) %>
|
1
|
+
<% data_columns = params[:group_by].present? ? [params[:group_by]] : @klass.display_columns %>
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
<
|
3
|
+
<div class="well well-sm">
|
4
|
+
<%= form_tag url_for(params.except(:start_date, :to).to_unsafe_hash), method: "get", id: "search-form" do %>
|
5
|
+
<div>
|
6
|
+
<label style="margin-right: 10px;">Group By: <%= select_tag :group_by, options_for_select([["All", nil]] + @klass.display_columns.map{|x| [x.titleize.sub("Url ", "URL "), x]} , params[:group_by]) %></label>
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
<label style="margin-right: 10px;">From: <%= date_field_tag :start_date, @start_date %></label>
|
9
|
+
<label style="margin-right: 10px;">To: <%= date_field_tag :end_date, @end_date %></label>
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
<label style="margin-right: 10px;">Search: <%= text_field_tag :search, params[:search] %></label>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<% if params[:filter] %>
|
15
|
+
<div>
|
16
|
+
<label>Active Filter:</label>
|
17
|
+
<div class="badge badge-primary" style="margin-top: 5px; margin-bottom: 10px;">
|
18
|
+
<% filter_col, filter_val = params[:filter].split("==") %>
|
19
|
+
<%= filter_col.titleize.sub("Url ", "URL ") %> = "<%= filter_val %>"
|
20
|
+
</div>
|
21
|
+
<%= link_to "Remove Filter", url_for(params.to_unsafe_h.merge(filter: nil)) %>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
15
24
|
|
16
25
|
<div>
|
17
|
-
<%= link_to "Today", url_for(params.merge(start_date: Date.today, end_date: Date.today).to_unsafe_hash) %>
|
26
|
+
<%= link_to "Today", url_for(params.merge(start_date: Date.today, end_date: Date.today).to_unsafe_hash), style: ("font-weight: bold;" if @start_date == Date.today && @end_date == Date.today) %>
|
27
|
+
|
|
28
|
+
<% yesterday = Date.today - 1.day %>
|
29
|
+
<%= link_to "Yesterday", url_for(params.merge(start_date: yesterday, end_date: yesterday).to_unsafe_hash), style: ("font-weight: bold;" if @start_date == yesterday && @end_date == yesterday) %>
|
18
30
|
|
|
19
|
-
<%= link_to "
|
31
|
+
<%= link_to "Last 7 days", url_for(params.merge(start_date: 7.days.ago.to_date, end_date: Date.today).to_unsafe_hash), style: ("font-weight: bold;" if @start_date == 7.days.ago.to_date && @end_date == Date.today) %>
|
20
32
|
|
|
21
|
-
<%= link_to "Last
|
33
|
+
<%= link_to "Last 30 days", url_for(params.merge(start_date: 30.days.ago.to_date, end_date: Date.today).to_unsafe_hash), style: ("font-weight: bold;" if @start_date == 30.days.ago.to_date && @end_date == Date.today) %>
|
22
34
|
|
|
23
|
-
<%= link_to "Last
|
35
|
+
<%= link_to "Last 3 Months", url_for(params.merge(start_date: 3.months.ago.to_date, end_date: Date.today).to_unsafe_hash), style: ("font-weight: bold;" if @start_date == 3.months.ago.to_date && @end_date == Date.today) %>
|
36
|
+
|
|
37
|
+
<%= link_to "Last 6 Months", url_for(params.merge(start_date: 6.months.ago.to_date, end_date: Date.today).to_unsafe_hash), style: ("font-weight: bold;" if @start_date == 6.months.ago.to_date && @end_date == Date.today) %>
|
38
|
+
|
|
39
|
+
<%= link_to "Last Year", url_for(params.merge(start_date: 1.year.ago.to_date, end_date: Date.today).to_unsafe_hash), style: ("font-weight: bold;" if @start_date == 1.year.ago.to_date && @end_date == Date.today) %>
|
24
40
|
</div>
|
25
|
-
|
41
|
+
</div>
|
26
42
|
|
27
|
-
<h2>
|
43
|
+
<h2>Requests by <%= params[:type].titleize %></h2>
|
28
44
|
|
29
|
-
<table class="table table-striped table-condensed
|
45
|
+
<table class="table table-striped table-condensed">
|
30
46
|
<thead>
|
31
|
-
<%
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
<span title="Sort column">↕</span>
|
36
|
-
</th>
|
37
|
-
<% end %>
|
38
|
-
<% else %>
|
39
|
-
<td>
|
40
|
-
<%= params[:group_by].titleize.sub("Url ", "URL ") %>
|
41
|
-
<span title="Sort column">↕</span>
|
42
|
-
</td>
|
47
|
+
<% data_columns.each do |header| %>
|
48
|
+
<th>
|
49
|
+
<%= header.titleize.sub("Url ", "URL ") %>
|
50
|
+
</th>
|
43
51
|
<% end %>
|
44
52
|
|
45
53
|
<th>
|
46
|
-
Total
|
47
|
-
|
54
|
+
Total
|
55
|
+
</th>
|
56
|
+
|
57
|
+
<th>
|
58
|
+
Prev Period Difference
|
48
59
|
</th>
|
49
60
|
</thead>
|
50
61
|
|
51
62
|
<tbody>
|
52
|
-
<% @
|
53
|
-
<% first_record = records.first %>
|
54
|
-
|
63
|
+
<% @results.each_with_index do |row, row_index| %>
|
55
64
|
<tr>
|
56
|
-
<%
|
57
|
-
<% @klass.display_columns.each do |col_name| %>
|
58
|
-
<td>
|
59
|
-
<%= first_record.send(col_name) %>
|
60
|
-
</td>
|
61
|
-
<% end %>
|
62
|
-
<% else %>
|
65
|
+
<% row[0..-2].each_with_index do |value, col_index| %>
|
63
66
|
<td>
|
64
|
-
|
67
|
+
<% filter_param = "#{data_columns[col_index]}==#{value}" %>
|
68
|
+
<%= link_to (value || ""), url_for(params.to_unsafe_h.merge(filter: filter_param)), title: "Filter" %>
|
65
69
|
</td>
|
66
70
|
<% end %>
|
67
71
|
|
68
72
|
<td>
|
69
|
-
<% total =
|
70
|
-
<%= total %>
|
73
|
+
<% total = row.last %>
|
74
|
+
<%= number_with_delimiter(total) %>
|
75
|
+
</td>
|
76
|
+
|
77
|
+
<td>
|
78
|
+
<% if @prev_period_results.nil? %>
|
79
|
+
<%
|
80
|
+
diff_params = {
|
81
|
+
format: :json,
|
82
|
+
type: params[:type],
|
83
|
+
start_date: @start_date,
|
84
|
+
end_date: @end_date,
|
85
|
+
conditions: {},
|
86
|
+
}
|
87
|
+
|
88
|
+
data_columns.each_with_index do |col, col_index|
|
89
|
+
diff_params[:conditions][col] = row[col_index]
|
90
|
+
end
|
91
|
+
|
92
|
+
placeholder_id = "diff-placeholder-#{row_index}"
|
93
|
+
%>
|
71
94
|
|
72
|
-
|
73
|
-
<% diff = total - prev_period_records.sum(&:total) %>
|
95
|
+
<button type="button" class="load-difference is-link" data-url="<%= difference_tracked_requests_path(diff_params) %>" data-placeholder-id="<%= placeholder_id %>">Load Difference</button>
|
74
96
|
|
75
|
-
|
76
|
-
(+<%= diff %>)
|
97
|
+
<div id="<%= placeholder_id %>"></div>
|
77
98
|
<% else %>
|
78
|
-
|
99
|
+
<% prev_period_row_index = @prev_period_results.index{|prev_period_row| row[0..-2] == prev_period_row[0..-2] } %>
|
100
|
+
|
101
|
+
<% if prev_period_row_index.nil? %>
|
102
|
+
+<%= number_with_delimiter(total) %>
|
103
|
+
<% else %>
|
104
|
+
<% prev_period_row = @prev_period_results.delete_at(prev_period_row_index) %>
|
105
|
+
|
106
|
+
<% prev_period_total = prev_period_row.last %>
|
107
|
+
<% diff = total - prev_period_total %>
|
108
|
+
|
109
|
+
<% if diff >= 0 %>
|
110
|
+
+<%= number_with_delimiter(diff) %>
|
111
|
+
<% else %>
|
112
|
+
<%= number_with_delimiter(diff) %>
|
113
|
+
<% end %>
|
114
|
+
<% end %>
|
79
115
|
<% end %>
|
80
116
|
</td>
|
81
117
|
</tr>
|
@@ -83,19 +119,62 @@
|
|
83
119
|
</tbody>
|
84
120
|
</table>
|
85
121
|
|
122
|
+
<div style="text-align: center;">
|
123
|
+
<% if params[:page].present? && params[:page].to_i > 1 %>
|
124
|
+
<%= link_to "Prev", url_for(params.to_unsafe_h.merge(page: pagination_page_number-1)) %>
|
125
|
+
|
|
126
|
+
<% end %>
|
127
|
+
|
128
|
+
<% if @results.size >= RailsLocalAnalytics::DashboardController::PER_PAGE_LIMIT %>
|
129
|
+
<%= link_to "Next", url_for(params.to_unsafe_h.merge(page: pagination_page_number+1)) %>
|
130
|
+
<% end %>
|
131
|
+
</div>
|
132
|
+
|
86
133
|
<script>
|
87
|
-
|
88
|
-
var
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
134
|
+
document.addEventListener("DOMContentLoaded", function(){
|
135
|
+
var form = document.querySelector("form#search-form")
|
136
|
+
|
137
|
+
document.querySelectorAll("form#search-form select, form#search-form input").forEach(function(el){
|
138
|
+
el.addEventListener("change", function(){
|
139
|
+
form.submit();
|
140
|
+
});
|
141
|
+
});
|
142
|
+
|
143
|
+
document.querySelectorAll("button.load-difference").forEach(function(el){
|
144
|
+
el.addEventListener("click", async function(){
|
145
|
+
el.disabled = true;
|
146
|
+
|
147
|
+
var csrf_token = document.querySelector('meta[name="csrf-token"]').content;
|
148
|
+
|
149
|
+
var request = new Request(
|
150
|
+
el.getAttribute("data-url"),
|
151
|
+
{
|
152
|
+
method: "GET",
|
153
|
+
headers: {
|
154
|
+
"Content-Type": "application/json",
|
155
|
+
Accept: "application/json",
|
156
|
+
"X-CSRF-Token": csrf_token,
|
157
|
+
},
|
158
|
+
},
|
159
|
+
);
|
160
|
+
|
161
|
+
var response = await fetch(request)
|
162
|
+
.then(function(response){
|
163
|
+
response.json().then(function(parsed_response){
|
164
|
+
el.style.display = "none";
|
165
|
+
|
166
|
+
var placeholderEl = document.querySelector("#" + el.getAttribute("data-placeholder-id"));
|
167
|
+
|
168
|
+
if(parsed_response.difference >= 1){
|
169
|
+
placeholderEl.innerHTML = "+" + parsed_response.difference.toLocaleString();
|
170
|
+
}else{
|
171
|
+
placeholderEl.innerHTML = "" + parsed_response.difference.toLocaleString();
|
172
|
+
}
|
173
|
+
});
|
174
|
+
|
175
|
+
});
|
176
|
+
|
177
|
+
});
|
99
178
|
});
|
100
179
|
});
|
101
180
|
</script>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
if Browser::VERSION.to_f < 6.0
|
2
|
+
Browser::Base.class_eval do
|
3
|
+
def chromium_based?
|
4
|
+
false
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
Browser::Chrome.class_eval do
|
9
|
+
def chromium_based?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Browser::Edge.class_eval do
|
15
|
+
def chromium_based?
|
16
|
+
match? && ua.match?(/\bEdg\b/)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
RailsLocalAnalytics::Engine.routes.draw do
|
2
|
-
get "/tracked_requests", to: "dashboard#index"
|
2
|
+
get "/tracked_requests/:type", to: "dashboard#index", as: :tracked_requests
|
3
|
+
get "/tracked_requests/:type/difference", to: "dashboard#difference", as: :difference_tracked_requests, constraints: {format: :json}
|
3
4
|
|
4
|
-
root to: "
|
5
|
+
root to: "application#root"
|
5
6
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_local_analytics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Weston Ganger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -92,18 +92,17 @@ files:
|
|
92
92
|
- README.md
|
93
93
|
- Rakefile
|
94
94
|
- app/assets/config/rails_local_analytics_manifest.js
|
95
|
-
- app/assets/javascripts/rails_local_analytics/application.js
|
96
95
|
- app/assets/stylesheets/rails_local_analytics/application.css
|
97
96
|
- app/controllers/rails_local_analytics/application_controller.rb
|
98
97
|
- app/controllers/rails_local_analytics/dashboard_controller.rb
|
99
98
|
- app/jobs/rails_local_analytics/application_job.rb
|
100
99
|
- app/jobs/rails_local_analytics/record_request_job.rb
|
101
|
-
- app/lib/rails_local_analytics/histogram.rb
|
102
100
|
- app/models/rails_local_analytics/application_record.rb
|
103
101
|
- app/models/tracked_requests_by_day_page.rb
|
104
102
|
- app/models/tracked_requests_by_day_site.rb
|
105
103
|
- app/views/layouts/rails_local_analytics/application.html.erb
|
106
104
|
- app/views/rails_local_analytics/dashboard/index.html.erb
|
105
|
+
- config/initializers/browser_monkey_patches.rb
|
107
106
|
- config/routes.rb
|
108
107
|
- lib/rails_local_analytics.rb
|
109
108
|
- lib/rails_local_analytics/engine.rb
|
@@ -1,47 +0,0 @@
|
|
1
|
-
module RailsLocalAnalytics
|
2
|
-
class Histogram
|
3
|
-
attr_reader :bars, :from_date, :to_date
|
4
|
-
|
5
|
-
def initialize(scope, from_date, to_date)
|
6
|
-
@scope = scope
|
7
|
-
@from_date, @to_date = from_date, to_date
|
8
|
-
@bars = scope.map { |record| Bar.new(record.date, record.total, self) }
|
9
|
-
fill_missing_days(@bars, @from_date, @to_date)
|
10
|
-
end
|
11
|
-
|
12
|
-
def fill_missing_days(bars, from, to)
|
13
|
-
i = 0
|
14
|
-
while (day = from + i) <= to
|
15
|
-
if !@bars[i] || @bars[i].label != day
|
16
|
-
@bars.insert(i, Bar.new(day, 0, self))
|
17
|
-
end
|
18
|
-
i += 1
|
19
|
-
end
|
20
|
-
@bars
|
21
|
-
end
|
22
|
-
|
23
|
-
def max_value
|
24
|
-
@max_total ||= bars.map(&:value).max
|
25
|
-
end
|
26
|
-
|
27
|
-
def total
|
28
|
-
@bars.reduce(0) { |sum, bar| sum += bar.value }
|
29
|
-
end
|
30
|
-
|
31
|
-
class Bar
|
32
|
-
attr_reader :label, :value, :histogram
|
33
|
-
|
34
|
-
def initialize(label, value, histogram)
|
35
|
-
@label, @value, @histogram = label, value, histogram
|
36
|
-
end
|
37
|
-
|
38
|
-
def height
|
39
|
-
if histogram.max_value > 0
|
40
|
-
(value.to_f / histogram.max_value).round(2)
|
41
|
-
else
|
42
|
-
0
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|