active_analytics 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +105 -15
- data/app/controllers/active_analytics/application_controller.rb +24 -4
- data/app/controllers/active_analytics/assets_controller.rb +29 -0
- data/app/controllers/active_analytics/pages_controller.rb +9 -10
- data/app/controllers/active_analytics/referrers_controller.rb +9 -5
- data/app/controllers/active_analytics/sites_controller.rb +4 -4
- data/app/models/active_analytics/views_per_day.rb +22 -3
- data/app/views/active_analytics/assets/_charts.css +249 -0
- data/app/{assets/stylesheets/active_analytics/style.css → views/active_analytics/assets/_style.css} +37 -38
- data/app/views/active_analytics/assets/application.css.erb +2 -0
- data/app/{assets/javascripts/active_analytics → views/active_analytics/assets}/application.js +1 -3
- data/app/views/active_analytics/assets/ariato.css +875 -0
- data/app/views/active_analytics/assets/ariato.js +322 -0
- data/app/views/active_analytics/pages/_table.html.erb +6 -17
- data/app/views/active_analytics/pages/index.html.erb +1 -1
- data/app/views/active_analytics/pages/show.html.erb +7 -8
- data/app/views/active_analytics/referrers/_table.html.erb +9 -2
- data/app/views/active_analytics/referrers/index.html.erb +2 -2
- data/app/views/active_analytics/referrers/show.html.erb +6 -3
- data/app/views/active_analytics/sites/_histogram.html.erb +9 -2
- data/app/views/active_analytics/sites/_histogram_header.html.erb +10 -0
- data/app/views/active_analytics/sites/show.html.erb +2 -2
- data/app/views/layouts/active_analytics/_footer.html.erb +3 -3
- data/app/views/layouts/active_analytics/_header.html.erb +1 -3
- data/app/views/layouts/active_analytics/application.html.erb +5 -3
- data/config/routes.rb +2 -1
- data/lib/active_analytics/version.rb +1 -1
- data/lib/active_analytics.rb +47 -4
- metadata +15 -11
- data/app/assets/javascripts/active_analytics/ariato.js +0 -746
- data/app/assets/stylesheets/active_analytics/application.css +0 -15
- data/app/assets/stylesheets/active_analytics/ariato.css +0 -3548
- data/app/assets/stylesheets/active_analytics/charts.css +0 -424
@@ -0,0 +1,322 @@
|
|
1
|
+
Ariato = {}
|
2
|
+
|
3
|
+
Ariato.launchWhenDomIsReady = function(root) {
|
4
|
+
if (document.readyState != "loading") {
|
5
|
+
Ariato.launch()
|
6
|
+
Ariato.launch(document, "aria-roledescription")
|
7
|
+
Ariato.launch(document, "data-ariato")
|
8
|
+
}
|
9
|
+
else
|
10
|
+
document.addEventListener("DOMContentLoaded", function() { Ariato.launchWhenDomIsReady(root) } )
|
11
|
+
}
|
12
|
+
|
13
|
+
Ariato.launch = function(root, attribute, parent) {
|
14
|
+
attribute || (attribute = "role")
|
15
|
+
var elements = (root || document).querySelectorAll("[" + attribute + "]")
|
16
|
+
for (var i = 0; i < elements.length; i++)
|
17
|
+
Ariato.start(elements[i], attribute, parent)
|
18
|
+
}
|
19
|
+
|
20
|
+
Ariato.mount = function() {
|
21
|
+
}
|
22
|
+
|
23
|
+
Ariato.start = function(element, attribute, parent) {
|
24
|
+
var names = element.getAttribute(attribute).split(" ")
|
25
|
+
for (var i = 0; i < names.length; i++) {
|
26
|
+
var name = names[i].charAt(0).toUpperCase() + names[i].slice(1) // Capitalize
|
27
|
+
var func = Ariato.stringToFunction("Ariato." + name) || Ariato.stringToFunction(name)
|
28
|
+
if (func instanceof Function)
|
29
|
+
Ariato.instanciate(func, element, parent)
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
Ariato.instanciate = function(func, element, parent) {
|
34
|
+
try {
|
35
|
+
controller = Object.create(func.prototype)
|
36
|
+
controller.parent = parent
|
37
|
+
controller.node = element
|
38
|
+
Ariato.initialize(controller, element)
|
39
|
+
func.call(controller, element)
|
40
|
+
} catch (ex) {
|
41
|
+
console.error(ex)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
Ariato.stringToFunction = function(fullName) {
|
46
|
+
var func = window, names = fullName.split(".")
|
47
|
+
for (var i = 0; i < names.length; i++)
|
48
|
+
if (!(func = func[names[i]]))
|
49
|
+
return null
|
50
|
+
return func
|
51
|
+
}
|
52
|
+
|
53
|
+
Ariato.initialize = function(controller, container) {
|
54
|
+
Ariato.listenEvents(container, controller)
|
55
|
+
Ariato.assignRoles(container, controller)
|
56
|
+
}
|
57
|
+
|
58
|
+
Ariato.listenEvents = function(root, controller) {
|
59
|
+
var elements = root.querySelectorAll("[data-event]")
|
60
|
+
for (var i = 0; i < elements.length; i++) {
|
61
|
+
elements[i].getAttribute("data-event").split(" ").forEach(function(eventAndAction) {
|
62
|
+
var array = eventAndAction.split("->")
|
63
|
+
Ariato.listenEvent(controller, elements[i], array[0], array[1])
|
64
|
+
})
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
Ariato.listenEvent = function(controller, element, event, action) {
|
69
|
+
if (controller[action] instanceof Function)
|
70
|
+
element.addEventListener(event, controller[action].bind(controller))
|
71
|
+
}
|
72
|
+
|
73
|
+
Ariato.findRoles = function(container) {
|
74
|
+
var roles = {}, elements = container.querySelectorAll("[data-role]")
|
75
|
+
for (var i = 0; i < elements.length; i++) {
|
76
|
+
var name = elements[i].getAttribute("data-role")
|
77
|
+
roles[name] ? roles[name].push(elements[i]) : roles[name] = [elements[i]]
|
78
|
+
}
|
79
|
+
return roles
|
80
|
+
}
|
81
|
+
|
82
|
+
Ariato.assignRoles = function(container, controller) {
|
83
|
+
controller.roles = Ariato.findRoles(container)
|
84
|
+
for (var name in controller.roles)
|
85
|
+
if (controller.roles[name].length == 1)
|
86
|
+
controller[name] = controller.roles[name][0]
|
87
|
+
}
|
88
|
+
|
89
|
+
Ariato.Dialog = function(node) {
|
90
|
+
node.setAttribute("hidden", true)
|
91
|
+
node.addEventListener("open", this.open.bind(this))
|
92
|
+
node.addEventListener("close", this.close.bind(this))
|
93
|
+
node.addEventListener("keydown", this.keydown.bind(this))
|
94
|
+
}
|
95
|
+
|
96
|
+
Ariato.Dialog.open = function(elementOrId) {
|
97
|
+
var dialog = elementOrId instanceof Element ? elementOrId : document.getElementById(elementOrId)
|
98
|
+
dialog && dialog.dispatchEvent(new CustomEvent("open"))
|
99
|
+
}
|
100
|
+
|
101
|
+
Ariato.Dialog.close = function(button) {
|
102
|
+
var dialog = Ariato.Dialog.current()
|
103
|
+
if (dialog && dialog.node.contains(button))
|
104
|
+
dialog.close()
|
105
|
+
}
|
106
|
+
|
107
|
+
Ariato.Dialog.closeCurrent = function() {
|
108
|
+
var dialog = Ariato.Dialog.current()
|
109
|
+
dialog && dialog.close()
|
110
|
+
}
|
111
|
+
|
112
|
+
Ariato.Dialog.replace = function(elementOrId) {
|
113
|
+
Ariato.Dialog.closeCurrent()
|
114
|
+
Ariato.Dialog.open(elementOrId)
|
115
|
+
}
|
116
|
+
|
117
|
+
Ariato.Dialog.close = function(button) {
|
118
|
+
var dialog = Ariato.Dialog.current()
|
119
|
+
if (dialog && dialog.node.contains(button))
|
120
|
+
dialog.close()
|
121
|
+
}
|
122
|
+
|
123
|
+
Ariato.Dialog.list = []
|
124
|
+
|
125
|
+
Ariato.Dialog.current = function() {
|
126
|
+
return this.list[this.list.length - 1]
|
127
|
+
}
|
128
|
+
|
129
|
+
Ariato.Dialog.prototype.open = function(event) {
|
130
|
+
Ariato.Dialog.list.push(this)
|
131
|
+
document.addEventListener("focus", this.bindedLimitFocusScope = this.limitFocusScope.bind(this), true)
|
132
|
+
this.initiator = document.activeElement
|
133
|
+
this.node.removeAttribute("hidden")
|
134
|
+
|
135
|
+
this.lockScrolling()
|
136
|
+
this.createBackdrop()
|
137
|
+
this.createFocusStoppers()
|
138
|
+
this.focusFirstDescendant(this.node)
|
139
|
+
}
|
140
|
+
|
141
|
+
Ariato.Dialog.prototype.close = function(event) {
|
142
|
+
document.removeEventListener("focus", this.bindedLimitFocusScope, true)
|
143
|
+
this.node.setAttribute("hidden", true)
|
144
|
+
this.removeFocusStoppers()
|
145
|
+
this.removeBackdrop()
|
146
|
+
this.unlockScrolling()
|
147
|
+
this.initiator.focus()
|
148
|
+
Ariato.Dialog.list.pop()
|
149
|
+
}
|
150
|
+
|
151
|
+
Ariato.Dialog.prototype.keydown = function(event) {
|
152
|
+
if (event.key == "Escape")
|
153
|
+
this.close()
|
154
|
+
}
|
155
|
+
|
156
|
+
Ariato.Dialog.prototype.focusFirstDescendant = function(parent) {
|
157
|
+
var focusable = ["A", "BUTTON", "INPUT", "SELECT", "TEXTAREA"]
|
158
|
+
|
159
|
+
for (var i = 0; i < parent.children.length; i++) {
|
160
|
+
var child = parent.children[i]
|
161
|
+
if (focusable.indexOf(child.nodeName) != -1 && !child.disabled && child.type != "hidden") {
|
162
|
+
child.focus()
|
163
|
+
return child
|
164
|
+
}
|
165
|
+
else {
|
166
|
+
var focus = this.focusFirstDescendant(child)
|
167
|
+
if (focus) return focus
|
168
|
+
}
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
Ariato.Dialog.prototype.limitFocusScope = function(event) {
|
173
|
+
if (this == Ariato.Dialog.current())
|
174
|
+
if (!this.node.contains(event.target))
|
175
|
+
this.focusFirstDescendant(this.node)
|
176
|
+
}
|
177
|
+
|
178
|
+
Ariato.Dialog.prototype.lockScrolling = function() {
|
179
|
+
document.body.style.position = "fixed";
|
180
|
+
document.body.style.top = "-" + window.scrollY + "px";
|
181
|
+
}
|
182
|
+
|
183
|
+
Ariato.Dialog.prototype.unlockScrolling = function() {
|
184
|
+
var scrollY = document.body.style.top
|
185
|
+
document.body.style.position = ""
|
186
|
+
document.body.style.top = ""
|
187
|
+
window.scrollTo(0, parseInt(scrollY || "0") * -1)
|
188
|
+
}
|
189
|
+
|
190
|
+
Ariato.Dialog.prototype.createFocusStoppers = function() {
|
191
|
+
this.node.parentNode.insertBefore(this.focusStopper1 = document.createElement("div"), this.node)
|
192
|
+
this.focusStopper1.tabIndex = 0
|
193
|
+
|
194
|
+
this.node.parentNode.insertBefore(this.focusStopper2 = document.createElement("div"), this.node.nextSibling)
|
195
|
+
this.focusStopper2.tabIndex = 0
|
196
|
+
}
|
197
|
+
|
198
|
+
Ariato.Dialog.prototype.removeFocusStoppers = function() {
|
199
|
+
this.focusStopper1 && this.focusStopper1.parentNode.removeChild(this.focusStopper1)
|
200
|
+
this.focusStopper2 && this.focusStopper2.parentNode.removeChild(this.focusStopper2)
|
201
|
+
}
|
202
|
+
|
203
|
+
Ariato.Dialog.prototype.createBackdrop = function() {
|
204
|
+
this.backdrop = document.createElement("div")
|
205
|
+
this.backdrop.classList.add("dialog-backdrop")
|
206
|
+
this.node.parentNode.insertBefore(this.backdrop, this.node)
|
207
|
+
this.backdrop.appendChild(this.node)
|
208
|
+
}
|
209
|
+
|
210
|
+
Ariato.Dialog.prototype.removeBackdrop = function() {
|
211
|
+
this.backdrop.parentNode.insertBefore(this.node, this.backdrop)
|
212
|
+
this.backdrop.parentNode.removeChild(this.backdrop)
|
213
|
+
this.backdrop = null
|
214
|
+
}
|
215
|
+
|
216
|
+
Ariato.Alertdialog = Ariato.Dialog
|
217
|
+
|
218
|
+
Ariato.MenuButton = function(node) {
|
219
|
+
this.node = this.button = node
|
220
|
+
this.menu = document.getElementById(this.button.getAttribute("aria-controls"))
|
221
|
+
|
222
|
+
this.menu.addEventListener("keydown", this.keydown.bind(this))
|
223
|
+
this.button.addEventListener("keydown", this.keydown.bind(this))
|
224
|
+
|
225
|
+
this.button.addEventListener("click", this.clicked.bind(this))
|
226
|
+
window.addEventListener("click", this.windowClicked.bind(this), true)
|
227
|
+
}
|
228
|
+
|
229
|
+
Ariato.MenuButton.prototype.clicked = function(event) {
|
230
|
+
this.node.getAttribute("aria-expanded") == "true" ? this.close() : this.open()
|
231
|
+
}
|
232
|
+
|
233
|
+
Ariato.MenuButton.prototype.windowClicked = function() {
|
234
|
+
if (!this.node.contains(event.target) && this.node.getAttribute("aria-expanded") == "true")
|
235
|
+
this.close()
|
236
|
+
}
|
237
|
+
|
238
|
+
Ariato.MenuButton.prototype.open = function() {
|
239
|
+
this.button.setAttribute("aria-expanded", "true")
|
240
|
+
this.menu.style.display = "block"
|
241
|
+
}
|
242
|
+
|
243
|
+
Ariato.MenuButton.prototype.close = function() {
|
244
|
+
this.button.setAttribute("aria-expanded", "false")
|
245
|
+
this.menu.style.display = null
|
246
|
+
}
|
247
|
+
|
248
|
+
Ariato.MenuButton.prototype.keydown = function(event) {
|
249
|
+
switch(event.key) {
|
250
|
+
case "Escape":
|
251
|
+
this.close()
|
252
|
+
break
|
253
|
+
case "ArrowDown":
|
254
|
+
event.preventDefault()
|
255
|
+
this.focusNextItem()
|
256
|
+
break
|
257
|
+
case "ArrowUp":
|
258
|
+
event.preventDefault()
|
259
|
+
this.focusPreviousItem()
|
260
|
+
break
|
261
|
+
case "Tab":
|
262
|
+
this.close()
|
263
|
+
case "Home":
|
264
|
+
case "PageUp":
|
265
|
+
event.preventDefault()
|
266
|
+
this.items()[0].focus()
|
267
|
+
break
|
268
|
+
case "End":
|
269
|
+
case "PageDown":
|
270
|
+
event.preventDefault()
|
271
|
+
var items = this.items()
|
272
|
+
items[items.length-1].focus()
|
273
|
+
break
|
274
|
+
}
|
275
|
+
}
|
276
|
+
|
277
|
+
Ariato.MenuButton.prototype.items = function() {
|
278
|
+
return this.menu.querySelectorAll("[role=menuitem]")
|
279
|
+
}
|
280
|
+
|
281
|
+
Ariato.MenuButton.prototype.currentItem = function() {
|
282
|
+
return this.menu.querySelector("[role=menuitem]:focus")
|
283
|
+
}
|
284
|
+
|
285
|
+
Ariato.MenuButton.prototype.nextItem = function() {
|
286
|
+
var items = this.items()
|
287
|
+
var current = this.currentItem()
|
288
|
+
if (!current) return items[0]
|
289
|
+
for (var i = 0; i < items.length; i++) {
|
290
|
+
if (items[i] == current)
|
291
|
+
return items[i+1]
|
292
|
+
}
|
293
|
+
}
|
294
|
+
|
295
|
+
Ariato.MenuButton.prototype.previousItem = function() {
|
296
|
+
var items = this.items()
|
297
|
+
var current = this.currentItem()
|
298
|
+
if (!current) return items[0]
|
299
|
+
for (var i = 0; i < items.length; i++) {
|
300
|
+
if (items[i] == current)
|
301
|
+
return items[i-1]
|
302
|
+
}
|
303
|
+
}
|
304
|
+
|
305
|
+
Ariato.MenuButton.prototype.focusNextItem = function() {
|
306
|
+
var item = this.nextItem()
|
307
|
+
item && item.focus()
|
308
|
+
}
|
309
|
+
|
310
|
+
Ariato.MenuButton.prototype.focusPreviousItem = function() {
|
311
|
+
var item = this.previousItem()
|
312
|
+
item && item.focus()
|
313
|
+
}
|
314
|
+
|
315
|
+
Ariato.Menu = function(node) {
|
316
|
+
var button = this.labelledBy()
|
317
|
+
button && new Ariato.MenuButton(button)
|
318
|
+
}
|
319
|
+
|
320
|
+
Ariato.Menu.prototype.labelledBy = function() {
|
321
|
+
return document.getElementById(this.node.getAttribute("aria-labelledby"))
|
322
|
+
}
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
<% if pages.empty? %>
|
3
2
|
<div class="is-empty">
|
4
3
|
<span>no data</span>
|
@@ -8,26 +7,16 @@
|
|
8
7
|
<% for page in pages %>
|
9
8
|
<tr>
|
10
9
|
<td>
|
11
|
-
<% if page.
|
12
|
-
|
13
|
-
<%= link_to page.path, page_path(site: page.host, page: page_to_params(page.path), from: params[:from], to: params[:to]) %>
|
14
|
-
<% else %>
|
15
|
-
<%= site_icon page.host %>
|
16
|
-
<%= link_to page.host, site_path(site: page.host, from: params[:from], to: params[:to]) %>
|
17
|
-
<small>(page not provided <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin">?</a>)</small>
|
18
|
-
<% end %>
|
19
|
-
<% elsif page.host.present? && page.path.present? %>
|
20
|
-
<%= link_to(referrer_path(site: params[:site], referrer: page.host, from: params[:from], to: params[:to])) { site_icon page.host } %>
|
21
|
-
<%= link_to page.url, referrer_path(site: params[:site], referrer: page.host, from: params[:from], to: params[:to]) %>
|
22
|
-
<% elsif page.host.present? %>
|
23
|
-
<%= site_icon page.host %>
|
24
|
-
<%= page.host %>
|
10
|
+
<% if page.path.present? %>
|
11
|
+
<%= link_to page.path, page_path(site: page.host, page: page_to_params(page.path), from: params[:from], to: params[:to]) %>
|
25
12
|
<% else %>
|
26
|
-
|
13
|
+
<%= site_icon page.host %>
|
14
|
+
<%= link_to page.host, site_path(site: page.host, from: params[:from], to: params[:to]) %>
|
15
|
+
<small>(page not provided <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin">?</a>)</small>
|
27
16
|
<% end %>
|
28
17
|
</td>
|
29
18
|
<td class="number"><%= format_view_count page.total %></td>
|
30
19
|
</tr>
|
31
20
|
<% end %>
|
32
21
|
</table>
|
33
|
-
<% end %>
|
22
|
+
<% end %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<section class="card">
|
2
|
-
|
2
|
+
<%= render "/active_analytics/sites/histogram_header", histogram: @histogram, previous_histogram: @previous_histogram %>
|
3
3
|
<%= render "/active_analytics/sites/histogram", histogram: @histogram %>
|
4
4
|
</section>
|
5
5
|
|
@@ -1,21 +1,20 @@
|
|
1
1
|
<section>
|
2
|
-
<h2
|
2
|
+
<h2>
|
3
|
+
<%= page_from_params %>
|
4
|
+
<%= link_to "", File.join("https://", params[:site], page_from_params), target: "_blank" %>
|
5
|
+
</h2>
|
3
6
|
</section>
|
4
7
|
|
5
8
|
<section class="card">
|
6
|
-
|
9
|
+
<%= render "/active_analytics/sites/histogram_header", histogram: @histogram, previous_histogram: @previous_histogram %>
|
7
10
|
<%= render "/active_analytics/sites/histogram", histogram: @histogram %>
|
8
11
|
</section>
|
9
12
|
|
10
13
|
<ul class="grid-auto">
|
11
14
|
<li class="card">
|
12
|
-
<!-- <section>
|
13
|
-
<h3>Top sources</h3>
|
14
|
-
<%= render "/active_analytics/referrers/table", referrers: @referrers %>
|
15
|
-
</section> -->
|
16
15
|
<section>
|
17
16
|
<h3>Previous page</h3>
|
18
|
-
<%= render "table",
|
17
|
+
<%= render "/active_analytics/referrers/table", referrers: @previous_pages %>
|
19
18
|
</section>
|
20
19
|
</li>
|
21
20
|
|
@@ -23,4 +22,4 @@
|
|
23
22
|
<h3>Next page</h3>
|
24
23
|
<%= render "table", pages: @next_pages %>
|
25
24
|
</li>
|
26
|
-
</ul>
|
25
|
+
</ul>
|
@@ -7,11 +7,18 @@
|
|
7
7
|
<% for referrer in referrers %>
|
8
8
|
<tr>
|
9
9
|
<td>
|
10
|
-
<% if referrer.
|
10
|
+
<% if referrer.try(:path) %>
|
11
|
+
<%= site_icon referrer.host %>
|
12
|
+
<% if referrer.host == params[:site] %>
|
13
|
+
<%= link_to referrer.path, page_path(site: referrer.host, page: page_to_params(referrer.path), from: params[:from], to: params[:to]) %>
|
14
|
+
<% else %>
|
15
|
+
<%= link_to referrer.url, referrer_path(site: params[:site], referrer: referrer.url.chomp("/"), from: params[:from], to: params[:to]) %>
|
16
|
+
<% end %>
|
17
|
+
<% elsif referrer.host %>
|
11
18
|
<%= site_icon referrer.host %>
|
12
19
|
<%= link_to referrer.host, referrer_path(site: params[:site], referrer: referrer.host, from: params[:from], to: params[:to]) %>
|
13
20
|
<% else %>
|
14
|
-
|
21
|
+
(None or direct)
|
15
22
|
<% end %>
|
16
23
|
</td>
|
17
24
|
<td class="number"><%= format_view_count referrer.total %></td>
|
@@ -1,9 +1,9 @@
|
|
1
1
|
<section class="card">
|
2
|
-
|
2
|
+
<%= render "/active_analytics/sites/histogram_header", histogram: @histogram, previous_histogram: @previous_histogram %>
|
3
3
|
<%= render "/active_analytics/sites/histogram", histogram: @histogram %>
|
4
4
|
</section>
|
5
5
|
|
6
6
|
<section class="card">
|
7
7
|
<h3>Top sources</h3>
|
8
8
|
<%= render "table", referrers: @referrers %>
|
9
|
-
</section>
|
9
|
+
</section>
|
@@ -1,15 +1,18 @@
|
|
1
1
|
<section>
|
2
|
-
<h2>
|
2
|
+
<h2>
|
3
|
+
Source: <%= params[:referrer] %>
|
4
|
+
<%= link_to "", File.join("https://", params[:referrer]), target: "_blank" %>
|
5
|
+
</h2>
|
3
6
|
</section>
|
4
7
|
|
5
8
|
<section class="card">
|
6
|
-
|
9
|
+
<%= render "/active_analytics/sites/histogram_header", histogram: @histogram, previous_histogram: @previous_histogram %>
|
7
10
|
<%= render "/active_analytics/sites/histogram", histogram: @histogram %>
|
8
11
|
</section>
|
9
12
|
<ul class="grid-auto">
|
10
13
|
<li class="card">
|
11
14
|
<h3>Source page</h3>
|
12
|
-
<%= render "/active_analytics/
|
15
|
+
<%= render "/active_analytics/referrers/table", referrers: @previous_pages %>
|
13
16
|
</li>
|
14
17
|
|
15
18
|
<li class="card">
|
@@ -3,11 +3,18 @@
|
|
3
3
|
<tbody>
|
4
4
|
<% for bar in histogram.bars %>
|
5
5
|
<tr>
|
6
|
-
<th scope="row"
|
7
|
-
|
6
|
+
<th scope="row">
|
7
|
+
<% if bar.height > 0 %>
|
8
|
+
<%= link_to bar.label.day, url_for(params.merge(from: bar.label.to_date, to: bar.label.to_date).to_unsafe_hash) %>
|
9
|
+
<% else %>
|
10
|
+
<%= bar.label.day %>
|
11
|
+
<% end %>
|
12
|
+
</th>
|
13
|
+
<td style="--size: <%= bar.height %>; --color: rgba(var(--color-grey-100), 1);">
|
8
14
|
<span class="data"><%= bar.value.to_i %></span>
|
9
15
|
<span class="tooltip"><small class="tooltip-date"><%= l bar.label, format: :long%></small><br/><%= format_view_count bar.value %> views</span>
|
10
16
|
</td>
|
17
|
+
</a>
|
11
18
|
</tr>
|
12
19
|
<% end %>
|
13
20
|
</tbody>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<h3>
|
2
|
+
<%= format_view_count current_total = histogram.total %> views
|
3
|
+
<% if (previous_total = previous_histogram.total) > 0 %>
|
4
|
+
<% if (diff = current_total - previous_total) > 0 %>
|
5
|
+
<small class="is-success">+<%= number_to_percentage diff * 100.0 / previous_total, precision: 0 %></small>
|
6
|
+
<% elsif (diff = current_total - previous_total) < 0 %>
|
7
|
+
<small class="is-danger"><%= number_to_percentage diff * 100.0 / previous_total, precision: 0 %></small>
|
8
|
+
<% end %>
|
9
|
+
<% end %>
|
10
|
+
</h3>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<section class="card">
|
2
|
-
|
2
|
+
<%= render "/active_analytics/sites/histogram_header", histogram: @histogram, previous_histogram: @previous_histogram %>
|
3
3
|
<%= render "/active_analytics/sites/histogram", histogram: @histogram %>
|
4
4
|
</section>
|
5
5
|
|
@@ -13,4 +13,4 @@
|
|
13
13
|
<h3>Top pages <%= link_to "view all", pages_path(from: params[:from], to: params[:to]) %></h3>
|
14
14
|
<%= render "/active_analytics/pages/table", pages: @pages %>
|
15
15
|
</li>
|
16
|
-
</ul>
|
16
|
+
</ul>
|
@@ -37,12 +37,12 @@
|
|
37
37
|
<header id="custom-range-title">Custom range</header>
|
38
38
|
<%= form_tag url_for(params.except(:from, :to).to_unsafe_hash), method: "get", id: "dateRangeForm" do %>
|
39
39
|
<div role="group" class="is-block">
|
40
|
-
<label>From <%=
|
41
|
-
<label>To <%=
|
40
|
+
<label>From <%= date_field_tag :from, params[:from] %></label>
|
41
|
+
<label>To <%= date_field_tag :to, params[:to] %></label>
|
42
42
|
</div>
|
43
43
|
<% end %>
|
44
44
|
<footer>
|
45
45
|
<%= submit_tag "Change date range", form: "dateRangeForm" %>
|
46
46
|
<button type="reset" onclick="Ariato.Dialog.close(this)">Close</button>
|
47
47
|
</footer>
|
48
|
-
</div>
|
48
|
+
</div>
|
@@ -1,7 +1,5 @@
|
|
1
1
|
<header data-ariato="ActiveAnalytics.Header">
|
2
|
-
<nav aria-label="site">
|
3
|
-
|
4
|
-
<%#= link_to "Analytics", active_analytics_path, role: "button" %>
|
2
|
+
<nav aria-label="site">
|
5
3
|
<%= link_to active_analytics_path, class: 'logo' do %>
|
6
4
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11 11">
|
7
5
|
<path class="a" d="M.5,5.5s0,2,1,2,1-2,1-2,0-2,1-2,1,2,1,2,0,2,1,2,1-2,1-2,0-2,1-2,1,2,1,2,0,2,1,2,1-2,1-2"/>
|
@@ -1,13 +1,15 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
|
-
<html>
|
2
|
+
<html class="active-analytics">
|
3
3
|
<head>
|
4
4
|
<title>Active analytics</title>
|
5
5
|
<meta name=viewport content="width=device-width, initial-scale=1">
|
6
6
|
<%= csrf_meta_tags %>
|
7
7
|
<%= csp_meta_tag %>
|
8
8
|
|
9
|
-
<%=
|
10
|
-
<%=
|
9
|
+
<%= tag.link rel: "stylesheet", href: asset_path(:ariato, format: :css) %>
|
10
|
+
<%= tag.link rel: "stylesheet", href: asset_path(:application, format: :css) %>
|
11
|
+
<%= tag.script "", src: asset_path(:ariato, format: :js) %>
|
12
|
+
<%= tag.script "", src: asset_path(:application, format: :js) %>
|
11
13
|
</head>
|
12
14
|
|
13
15
|
<body>
|
data/config/routes.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
ActiveAnalytics::Engine.routes.draw do
|
2
|
+
resources :assets, only: [:show]
|
2
3
|
get "/:site", to: "sites#show", as: :site, constraints: {site: /[^\/]+/}
|
3
4
|
|
4
5
|
# Referrers
|
5
6
|
get "/:site/referrers", to: "referrers#index", constraints: {site: /[^\/]+/}, as: :referrers
|
6
|
-
get "/:site/referrers
|
7
|
+
get "/:site/referrers/*referrer", to: "referrers#show", as: :referrer, constraints: {site: /[^\/]+/, referrer: /.+/}
|
7
8
|
|
8
9
|
# Pages
|
9
10
|
get "/:site/pages", to: "pages#index", constraints: {site: /[^\/]+/}, as: :pages
|
data/lib/active_analytics.rb
CHANGED
@@ -2,6 +2,22 @@ require "active_analytics/version"
|
|
2
2
|
require "active_analytics/engine"
|
3
3
|
|
4
4
|
module ActiveAnalytics
|
5
|
+
def self.redis_url=(string)
|
6
|
+
@redis_url = string
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.redis_url
|
10
|
+
@redis_url ||= ENV["ACTIVE_ANALYTICS_REDIS_URL"] || ENV["REDIS_URL"] || "redis://localhost"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.redis=(connection)
|
14
|
+
@redis = connection
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.redis
|
18
|
+
@redis ||= Redis.new(url: redis_url)
|
19
|
+
end
|
20
|
+
|
5
21
|
def self.record_request(request)
|
6
22
|
params = {
|
7
23
|
site: request.host,
|
@@ -9,12 +25,39 @@ module ActiveAnalytics
|
|
9
25
|
date: Date.today,
|
10
26
|
}
|
11
27
|
if request.referrer.present?
|
12
|
-
|
13
|
-
params[:referrer_host] = referrer_uri.host
|
14
|
-
params[:referrer_path] = referrer_uri.path
|
28
|
+
params[:referrer_host], params[:referrer_path] = ViewsPerDay.split_referrer(request.referrer)
|
15
29
|
end
|
16
30
|
ViewsPerDay.append(params)
|
17
31
|
rescue => ex
|
18
|
-
|
32
|
+
if Rails.env.development? || Rails.env.test?
|
33
|
+
raise ex
|
34
|
+
else
|
35
|
+
Rails.logger.error(ex.inspect)
|
36
|
+
Rails.logger.error(ex.backtrace.join("\n"))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
SEPARATOR = "|"
|
41
|
+
QUEUE = "ActiveAnalytics::Queue"
|
42
|
+
OLD_QUEUE = "ActiveAnalytics::OldQueue"
|
43
|
+
|
44
|
+
def self.queue_request(request)
|
45
|
+
keys = [request.host, request.path]
|
46
|
+
if request.referrer.present?
|
47
|
+
keys.concat(ViewsPerDay.split_referrer(request.referrer))
|
48
|
+
end
|
49
|
+
redis.hincrby(QUEUE, keys.join(SEPARATOR).downcase, 1)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.flush_queue
|
53
|
+
return if !redis.exists?(QUEUE)
|
54
|
+
cursor = 0
|
55
|
+
date = Date.today
|
56
|
+
redis.rename(QUEUE, OLD_QUEUE)
|
57
|
+
redis.hscan_each(OLD_QUEUE) do |key, count|
|
58
|
+
site, page, referrer_host, referrer_path = key.split(SEPARATOR)
|
59
|
+
ViewsPerDay.append(date: date, site: site, page: page, referrer_host: referrer_host, referrer_path: referrer_path, total: count.to_i)
|
60
|
+
end
|
61
|
+
redis.del(OLD_QUEUE)
|
19
62
|
end
|
20
63
|
end
|