rails_local_analytics 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![Screenshot 3](/screenshot_3.png)
|
29
29
|
|
30
|
+
![Screenshot 4](/screenshot_4.png)
|
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
|