analytic 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,8 +3,40 @@
3
3
  @tailwind utilities;
4
4
 
5
5
  @layer components {
6
+ .stat {
7
+ @apply flex gap-2 justify-between items-center;
8
+ }
9
+
10
+ .delta {
11
+ @apply font-normal flex gap-2 items-center;
12
+ }
13
+
14
+ .delta--neutral {
15
+ @apply text-slate-400;
16
+ }
17
+
18
+ .delta--positive {
19
+ @apply text-emerald-600;
20
+ }
21
+
22
+ .delta--negative {
23
+ @apply text-rose-600;
24
+ }
25
+
26
+ .pills {
27
+ @apply inline-flex gap-2 bg-slate-100 border-slate-200 rounded-full text-slate-600;
28
+ }
29
+
6
30
  .pill {
7
- @apply bg-slate-200 text-slate-800 hover:text-white hover:bg-indigo-600 rounded-full px-3 py-2;
31
+ @apply flex items-center justify-center gap-2 rounded-full px-6 py-2;
32
+ }
33
+
34
+ .pill--default {
35
+ @apply bg-slate-100 font-medium hover:bg-slate-200 hover:text-slate-800;
36
+ }
37
+
38
+ .pill--active {
39
+ @apply bg-white shadow font-semibold text-slate-800;
8
40
  }
9
41
 
10
42
  .card {
@@ -20,7 +52,7 @@
20
52
  }
21
53
 
22
54
  .card__content {
23
- @apply px-5 py-4
55
+ @apply px-5 py-4;
24
56
  }
25
57
 
26
58
  .card__title {
@@ -28,7 +60,11 @@
28
60
  }
29
61
 
30
62
  .card__value {
31
- @apply uppercase text-slate-800 font-extrabold text-lg;
63
+ @apply text-slate-800 font-extrabold text-lg;
64
+ }
65
+
66
+ .cards {
67
+ @apply grid gap-4 grid-cols-1 md:grid-cols-3;
32
68
  }
33
69
 
34
70
  .table {
@@ -1,25 +1,28 @@
1
1
  <%- provide(:title, @dashboard.name) %>
2
2
 
3
3
  <div class="space-y-4">
4
- <div class="flex gap-2">
5
- <%= link_to 'Today', analytic.dashboard_path(period: 'today'), class: 'pill' %>
6
- <%= link_to 'Yesterday', analytic.dashboard_path(period: 'yesterday'), class: 'pill' %>
7
- <%= link_to 'Week', analytic.dashboard_path(period: 'week'), class: 'pill' %>
8
- <%= link_to 'Month', analytic.dashboard_path(period: 'month'), class: 'pill' %>
9
- <%= link_to 'Year', analytic.dashboard_path(period: 'year'), class: 'pill' %>
4
+ <div class="pills">
5
+ <%= link_to 'All', analytic.dashboard_path, class: "pill #{@dashboard.period.nil? ? 'pill--active' : 'pill--default'}" %>
6
+ <%= link_to '24 hours', analytic.dashboard_path(period: '24h'), class: "pill #{@dashboard.period.eql?('24h') ? 'pill--active' : 'pill--default'}" %>
7
+ <%= link_to '7 days', analytic.dashboard_path(period: '7d'), class: "pill #{@dashboard.period.eql?('7d') ? 'pill--active' : 'pill--default'}" %>
8
+ <%= link_to '4 weeks', analytic.dashboard_path(period: '4w'), class: "pill #{@dashboard.period.eql?('4w') ? 'pill--active' : 'pill--default'}" %>
9
+ <%= link_to '12 months', analytic.dashboard_path(period: '12m'), class: "pill #{@dashboard.period.eql?('12m') ? 'pill--active' : 'pill--default'}" %>
10
10
  </div>
11
11
 
12
- <div class="grid grid-flow-col justify-stretch gap-4">
12
+ <div class="cards">
13
13
  <div class="card">
14
14
  <div class="card__header">
15
15
  <div class="card__title">
16
- <%= tag.i class: "fa-solid fa-users" %>
16
+ <%= fa_icon_tag("fa-solid fa-users") %>
17
17
  Visitors
18
18
  </div>
19
19
  </div>
20
20
  <div class="card__content">
21
21
  <div class="card__value">
22
- <%= number_with_delimiter @dashboard.distinct_visitors_count %>
22
+ <div class="stat">
23
+ <%= number_with_delimiter @dashboard.visitors.count %>
24
+ <%= delta_tag(@dashboard.visitors.delta) %>
25
+ </div>
23
26
  </div>
24
27
  </div>
25
28
  </div>
@@ -27,13 +30,16 @@
27
30
  <div class="card">
28
31
  <div class="card__header">
29
32
  <div class="card__title">
30
- <%= tag.i class: "fa-solid fa-globe" %>
33
+ <%= fa_icon_tag("fa-solid fa-globe") %>
31
34
  Sessions
32
35
  </div>
33
36
  </div>
34
37
  <div class="card__content">
35
38
  <div class="card__value">
36
- <%= number_with_delimiter @dashboard.distinct_sessions_count %>
39
+ <div class="stat">
40
+ <%= number_with_delimiter @dashboard.sessions.count %>
41
+ <%= delta_tag(@dashboard.sessions.delta) %>
42
+ </div>
37
43
  </div>
38
44
  </div>
39
45
  </div>
@@ -41,13 +47,16 @@
41
47
  <div class="card">
42
48
  <div class="card__header">
43
49
  <div class="card__title">
44
- <%= tag.i class: "fa-solid fa-eye" %>
50
+ <%= fa_icon_tag("fa-solid fa-eye") %>
45
51
  Views
46
52
  </div>
47
53
  </div>
48
54
  <div class="card__content">
49
55
  <div class="card__value">
50
- <%= number_with_delimiter @dashboard.count %>
56
+ <div class="stat">
57
+ <%= number_with_delimiter @dashboard.views.count %>
58
+ <%= delta_tag(@dashboard.views.delta) %>
59
+ </div>
51
60
  </div>
52
61
  </div>
53
62
  </div>
@@ -70,7 +79,7 @@
70
79
  </tr>
71
80
  </thead>
72
81
  <tbody>
73
- <%- @dashboard.pages.each do |(host, path, views)| -%>
82
+ <%- @dashboard.current.pages.each do |(host, path, views)| -%>
74
83
  <tr>
75
84
  <td><%= host %></td>
76
85
  <td><%= path %></td>
@@ -81,4 +90,42 @@
81
90
  </table>
82
91
  </div>
83
92
  </div>
93
+
94
+ <div class="cards">
95
+ <div class="card">
96
+ <div class="card__header">
97
+ <div class="card__title">
98
+ <%= tag.i class: "fa-solid fa-file-code" %>
99
+ Visitors
100
+ </div>
101
+ </div>
102
+ <div class="card__content">
103
+ <%= react(component: "Chart") %>
104
+ </div>
105
+ </div>
106
+
107
+ <div class="card">
108
+ <div class="card__header">
109
+ <div class="card__title">
110
+ <%= tag.i class: "fa-solid fa-globe" %>
111
+ Sessions
112
+ </div>
113
+ </div>
114
+ <div class="card__content">
115
+ <%= react(component: "Chart") %>
116
+ </div>
117
+ </div>
118
+
119
+ <div class="card">
120
+ <div class="card__header">
121
+ <div class="card__title">
122
+ <%= tag.i class: "fa-solid fa-eye" %>
123
+ Views
124
+ </div>
125
+ </div>
126
+ <div class="card__content">
127
+ <%= react(component: "Chart") %>
128
+ </div>
129
+ </div>
130
+ </div>
84
131
  </div>
@@ -6,7 +6,7 @@
6
6
  <%= csp_meta_tag %>
7
7
  <%= javascript_include_tag 'analytic/application' %>
8
8
  <%= stylesheet_link_tag 'analytic/application' %>
9
- <%= %>
9
+ <%= favicon_link_tag 'analytic/icon.svg', type: 'image/svg+xml' %>
10
10
  </head>
11
11
  <body class="bg-slate-50">
12
12
 
@@ -32,11 +32,5 @@
32
32
  <main class="container mx-auto px-4 py-8">
33
33
  <%= yield %>
34
34
  </main>
35
-
36
- <footer class="container mx-auto px-4 text-center">
37
- <div class="flex gap-2 justify-center">
38
- <%= time_tag(Time.current) %>
39
- </div>
40
- </footer>
41
35
  </body>
42
36
  </html>
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CreateAnalyticViews < ActiveRecord::Migration[7.1]
3
+ class CreateAnalyticEvents < ActiveRecord::Migration[7.1]
4
4
  def change
5
- create_table :analytic_views do |t|
5
+ create_table :analytic_events do |t|
6
6
  t.uuid :visitor_id, null: false
7
7
  t.uuid :session_id, null: false
8
8
  t.inet :ip, null: false
@@ -2,19 +2,51 @@
2
2
 
3
3
  module Analytic
4
4
  class Config
5
- # @return [String]
6
- attr_accessor :timezone
5
+ # @example
6
+ # config.time_zone = ActiveSupport::TimeZone['Tokyo']
7
+ #
8
+ # @!attribute [rw] time_zone
9
+ # @return [ActiveSupport::TimeZone]
10
+ attr_accessor :time_zone
7
11
 
8
- # @return [Integer]
12
+ # @example
13
+ # config.ip_v4_mask = 24
14
+ #
15
+ # @!attribute [rw] ip_v4_mask
16
+ # @return [Integer]
9
17
  attr_accessor :ip_v4_mask
10
18
 
11
- # @return [Integer]
19
+ # @example
20
+ # config.ip_v6_mask = 48
21
+ #
22
+ # @!attribute [rw] ip_v6_mask
23
+ # @return [Integer]
12
24
  attr_accessor :ip_v6_mask
13
25
 
26
+ # @example
27
+ # config.connects_to = database: { writing: :primary, reading: :replica }
28
+ #
29
+ # @!attribute [rw] connects_to
30
+ # @return [Hash, nil]
31
+ attr_accessor :connects_to
32
+
33
+ # @!attribute [rw] middleware
34
+ # @return [Array<Rack::Middleware>]
35
+ attr_accessor :middleware
36
+
37
+ # @example
38
+ # config.params = %i[utm_source utm_medium utm_campaign utm_content utm_term ref source]
39
+ #
40
+ # @!attribute [rw] params
41
+ # @return [Array<Symbol>]
42
+ attr_accessor :params
43
+
14
44
  def initialize
15
- @timezone = Time.zone
45
+ @time_zone = Time.zone
16
46
  @ip_v4_mask = 24 # e.g. 255.255.255.255 => '255.255.255.0/255.255.255.0'
17
47
  @ip_v6_mask = 48 # e.g. 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => 'ffff:ffff:ffff:0000:0000:0000:0000:0000'
48
+ @middleware = []
49
+ @params = %i[utm_source utm_medium utm_campaign utm_content utm_term ref source]
18
50
  end
19
51
 
20
52
  # @return [Boolean]
@@ -26,5 +58,15 @@ module Analytic
26
58
  def ip_v6_mask?
27
59
  @ip_v6_mask.present?
28
60
  end
61
+
62
+ # @return [Boolean]
63
+ def connects_to?
64
+ @connects_to.present?
65
+ end
66
+
67
+ # @param middleware [Rack::Middleware]
68
+ def use(*args, &block)
69
+ middleware << [args, block]
70
+ end
29
71
  end
30
72
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Analytic
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: analytic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-06 00:00:00.000000000 Z
11
+ date: 2024-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -34,10 +34,8 @@ files:
34
34
  - LICENSE
35
35
  - README.md
36
36
  - Rakefile
37
- - app/assets/builds/analytic/application.css
38
- - app/assets/builds/analytic/application.js
39
- - app/assets/builds/analytic/application.js.map
40
37
  - app/assets/config/analytic_manifest.js
38
+ - app/assets/images/analytic/icon.svg
41
39
  - app/controllers/analytic/application_controller.rb
42
40
  - app/controllers/analytic/dashboard_controller.rb
43
41
  - app/controllers/concerns/analytic/trackable.rb
@@ -47,7 +45,11 @@ files:
47
45
  - app/mailers/analytic/application_mailer.rb
48
46
  - app/models/analytic/application_record.rb
49
47
  - app/models/analytic/dashboard.rb
50
- - app/models/analytic/view.rb
48
+ - app/models/analytic/event.rb
49
+ - app/models/analytic/period.rb
50
+ - app/models/analytic/stat.rb
51
+ - app/packs/analytic/application/components/chart.tsx
52
+ - app/packs/analytic/application/components/index.tsx
51
53
  - app/packs/analytic/application/index.ts
52
54
  - app/packs/analytic/application/initializers/fontawesome.ts
53
55
  - app/packs/analytic/application/initializers/index.ts
@@ -58,7 +60,7 @@ files:
58
60
  - bin/dev
59
61
  - bin/rails
60
62
  - config/routes.rb
61
- - db/migrate/20240805210911_create_analytic_views.rb
63
+ - db/migrate/20240805210911_create_analytic_events.rb
62
64
  - lib/analytic.rb
63
65
  - lib/analytic/config.rb
64
66
  - lib/analytic/engine.rb
@@ -68,7 +70,7 @@ licenses:
68
70
  - MIT
69
71
  metadata:
70
72
  rubygems_mfa_required: 'true'
71
- post_install_message:
73
+ post_install_message:
72
74
  rdoc_options: []
73
75
  require_paths:
74
76
  - lib
@@ -83,8 +85,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
85
  - !ruby/object:Gem::Version
84
86
  version: '0'
85
87
  requirements: []
86
- rubygems_version: 3.5.11
87
- signing_key:
88
+ rubygems_version: 3.5.18
89
+ signing_key:
88
90
  specification_version: 4
89
91
  summary: Analytic
90
92
  test_files: []