analytic 0.2.0 → 0.5.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 +98 -9
- data/Rakefile +5 -0
- data/app/assets/images/analytic/icon.svg +1 -0
- data/app/controllers/analytic/dashboard_controller.rb +2 -1
- data/app/controllers/concerns/analytic/trackable.rb +50 -9
- data/app/helpers/analytic/application_helper.rb +42 -0
- data/app/jobs/analytic/track_job.rb +4 -2
- data/app/models/analytic/application_record.rb +2 -0
- data/app/models/analytic/dashboard.rb +61 -29
- data/app/models/analytic/{view.rb → event.rb} +4 -3
- data/app/models/analytic/period.rb +41 -0
- data/app/models/analytic/stat.rb +30 -0
- data/app/packs/analytic/application/components/chart.tsx +148 -0
- data/app/packs/analytic/application/components/index.tsx +21 -0
- data/app/packs/analytic/application/index.ts +1 -0
- data/app/packs/analytic/application/initializers/fontawesome.ts +7 -1
- data/app/packs/analytic/entrypoints/application.tailwind.css +39 -3
- data/app/views/analytic/dashboard/show.html.erb +61 -14
- data/app/views/layouts/analytic/application.html.erb +1 -7
- data/bin/rails +9 -7
- data/db/migrate/{20240805210911_create_analytic_views.rb → 20240805210911_create_analytic_events.rb} +6 -2
- data/lib/analytic/config.rb +63 -3
- data/lib/analytic/engine.rb +4 -0
- data/lib/analytic/version.rb +1 -1
- metadata +13 -11
- data/app/assets/builds/analytic/application.css +0 -810
- data/app/assets/builds/analytic/application.js +0 -54498
- data/app/assets/builds/analytic/application.js.map +0 -7
@@ -0,0 +1,148 @@
|
|
1
|
+
import { useMemo, type FC } from "react";
|
2
|
+
|
3
|
+
import { Group } from "@visx/group";
|
4
|
+
import { Bar } from "@visx/shape";
|
5
|
+
import { scaleBand, scaleLinear } from "@visx/scale";
|
6
|
+
import { AxisBottom, AxisLeft } from "@visx/axis";
|
7
|
+
import { GridColumns, GridRows } from "@visx/grid";
|
8
|
+
import { ParentSize } from "@visx/responsive";
|
9
|
+
|
10
|
+
const BAR_BG = "#6366f1"; // indigo-500
|
11
|
+
const CHART_BG = "#f8fafc"; // slate-50
|
12
|
+
const GRID_COLOR = "#e2e8f0"; // slate-200
|
13
|
+
const AXIS_TICK_COLOR = GRID_COLOR;
|
14
|
+
const AXIS_LINE_COLOR = GRID_COLOR;
|
15
|
+
|
16
|
+
const CHART_ML = 60; // px
|
17
|
+
const CHART_MB = 60; // px
|
18
|
+
const CHART_MT = 40; // px
|
19
|
+
const CHART_MR = 40; // px
|
20
|
+
|
21
|
+
const TICK_LABEL_PROPS = {
|
22
|
+
fill: "#718096", // slate-600
|
23
|
+
fontSize: 10,
|
24
|
+
fontFamily: `"ui-sans-serif", "system-ui", "sans-serif"`,
|
25
|
+
} as const;
|
26
|
+
|
27
|
+
type Entry = {
|
28
|
+
label: string;
|
29
|
+
value: number;
|
30
|
+
};
|
31
|
+
|
32
|
+
const ENTRIES: Entry[] = [
|
33
|
+
{ label: "Jan, 2024", value: 404 },
|
34
|
+
{ label: "Feb, 2024", value: 303 },
|
35
|
+
{ label: "Mar, 2024", value: 250 },
|
36
|
+
{ label: "Apr, 2024", value: 304 },
|
37
|
+
{ label: "May, 2024", value: 404 },
|
38
|
+
{ label: "Jun, 2024", value: 604 },
|
39
|
+
];
|
40
|
+
|
41
|
+
const Bars: FC<{
|
42
|
+
width: number;
|
43
|
+
height: number;
|
44
|
+
entries: Entry[];
|
45
|
+
}> = ({ width, height, entries }) => {
|
46
|
+
const xScale = useMemo(
|
47
|
+
() =>
|
48
|
+
scaleBand<string>({
|
49
|
+
range: [0, width],
|
50
|
+
round: true,
|
51
|
+
domain: entries.map(({ label }) => label),
|
52
|
+
padding: 0.4,
|
53
|
+
}),
|
54
|
+
[width]
|
55
|
+
);
|
56
|
+
const yScale = useMemo(
|
57
|
+
() =>
|
58
|
+
scaleLinear<number>({
|
59
|
+
range: [height, 0],
|
60
|
+
round: true,
|
61
|
+
domain: [0, Math.max(...entries.map(({ value }) => value))],
|
62
|
+
}),
|
63
|
+
[height]
|
64
|
+
);
|
65
|
+
|
66
|
+
return (
|
67
|
+
<>
|
68
|
+
<GridRows
|
69
|
+
scale={yScale}
|
70
|
+
width={width}
|
71
|
+
height={height}
|
72
|
+
stroke={GRID_COLOR}
|
73
|
+
/>
|
74
|
+
|
75
|
+
<GridColumns
|
76
|
+
scale={xScale}
|
77
|
+
width={width}
|
78
|
+
height={height}
|
79
|
+
stroke={GRID_COLOR}
|
80
|
+
/>
|
81
|
+
|
82
|
+
{entries.map((entry, index) => {
|
83
|
+
const barWidth = xScale.bandwidth();
|
84
|
+
const barHeight = height - yScale(entry.value);
|
85
|
+
const barX = xScale(entry.label);
|
86
|
+
const barY = height - barHeight;
|
87
|
+
|
88
|
+
return (
|
89
|
+
<Bar
|
90
|
+
key={index}
|
91
|
+
x={barX}
|
92
|
+
y={barY}
|
93
|
+
rx={8}
|
94
|
+
ry={8}
|
95
|
+
width={barWidth}
|
96
|
+
height={barHeight}
|
97
|
+
fill={BAR_BG}
|
98
|
+
/>
|
99
|
+
);
|
100
|
+
})}
|
101
|
+
|
102
|
+
<AxisBottom
|
103
|
+
top={height}
|
104
|
+
scale={xScale}
|
105
|
+
stroke={AXIS_LINE_COLOR}
|
106
|
+
tickStroke={AXIS_TICK_COLOR}
|
107
|
+
tickLabelProps={TICK_LABEL_PROPS}
|
108
|
+
/>
|
109
|
+
|
110
|
+
<AxisLeft
|
111
|
+
left={0}
|
112
|
+
scale={yScale}
|
113
|
+
stroke={AXIS_LINE_COLOR}
|
114
|
+
tickStroke={AXIS_TICK_COLOR}
|
115
|
+
tickLabelProps={TICK_LABEL_PROPS}
|
116
|
+
/>
|
117
|
+
</>
|
118
|
+
);
|
119
|
+
};
|
120
|
+
|
121
|
+
export const Chart: FC<{
|
122
|
+
height?: number;
|
123
|
+
entries?: Entry[];
|
124
|
+
}> = ({ height = 300, entries = ENTRIES }) => (
|
125
|
+
<ParentSize>
|
126
|
+
{({ width }) => (
|
127
|
+
<svg width={width} height={height}>
|
128
|
+
<rect
|
129
|
+
x={0}
|
130
|
+
y={0}
|
131
|
+
rx={4}
|
132
|
+
ry={4}
|
133
|
+
width={width}
|
134
|
+
height={height}
|
135
|
+
fill={CHART_BG}
|
136
|
+
/>
|
137
|
+
|
138
|
+
<Group top={CHART_MT} left={CHART_ML}>
|
139
|
+
<Bars
|
140
|
+
width={width - CHART_MR - CHART_ML}
|
141
|
+
height={height - CHART_MT - CHART_MB}
|
142
|
+
entries={entries}
|
143
|
+
/>
|
144
|
+
</Group>
|
145
|
+
</svg>
|
146
|
+
)}
|
147
|
+
</ParentSize>
|
148
|
+
);
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { type FC } from "react";
|
2
|
+
|
3
|
+
import { Chart } from "./chart";
|
4
|
+
import { createRoot } from "react-dom/client";
|
5
|
+
|
6
|
+
const COMPONENTS: Record<string, FC> = {
|
7
|
+
Chart,
|
8
|
+
};
|
9
|
+
|
10
|
+
document.addEventListener("DOMContentLoaded", () => {
|
11
|
+
const elements = document.querySelectorAll("[data-react]");
|
12
|
+
for (const element of elements) {
|
13
|
+
const root = createRoot(element);
|
14
|
+
|
15
|
+
const name = element.getAttribute("data-react");
|
16
|
+
|
17
|
+
const Component = COMPONENTS[name!];
|
18
|
+
|
19
|
+
root.render(<Component />);
|
20
|
+
}
|
21
|
+
});
|
@@ -10,14 +10,20 @@ import { faGlobe } from "@fortawesome/free-solid-svg-icons/faGlobe";
|
|
10
10
|
import { faHouse } from "@fortawesome/free-solid-svg-icons/faHouse";
|
11
11
|
import { faSliders } from "@fortawesome/free-solid-svg-icons/faSliders";
|
12
12
|
import { faUsers } from "@fortawesome/free-solid-svg-icons/faUsers";
|
13
|
+
import { faArrowUp } from "@fortawesome/free-solid-svg-icons/faArrowUp";
|
14
|
+
import { faArrowDown } from "@fortawesome/free-solid-svg-icons/faArrowDown";
|
15
|
+
import { faXmark } from "@fortawesome/free-solid-svg-icons/faXmark";
|
13
16
|
|
14
17
|
library.add(
|
18
|
+
faArrowDown,
|
19
|
+
faArrowUp,
|
15
20
|
faClock,
|
16
21
|
faEye,
|
22
|
+
faFileCode,
|
17
23
|
faGauge,
|
18
24
|
faGlobe,
|
19
25
|
faHouse,
|
20
26
|
faSliders,
|
21
27
|
faUsers,
|
22
|
-
|
28
|
+
faXmark
|
23
29
|
);
|
@@ -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
|
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
|
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="
|
5
|
-
<%= link_to '
|
6
|
-
<%= link_to '
|
7
|
-
<%= link_to '
|
8
|
-
<%= link_to '
|
9
|
-
<%= link_to '
|
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="
|
12
|
+
<div class="cards">
|
13
13
|
<div class="card">
|
14
14
|
<div class="card__header">
|
15
15
|
<div class="card__title">
|
16
|
-
<%=
|
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
|
-
|
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
|
-
<%=
|
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
|
-
|
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
|
-
<%=
|
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
|
-
|
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>
|
data/bin/rails
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
ENGINE_PATH = File.expand_path("../lib/analytic/engine", __dir__)
|
5
|
-
APP_PATH = File.expand_path("../spec/dummy/config/application", __dir__)
|
3
|
+
# frozen_string_literal: true
|
6
4
|
|
7
|
-
|
8
|
-
|
5
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
6
|
+
ENGINE_PATH = File.expand_path('../lib/analytic/engine', __dir__)
|
7
|
+
APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
|
9
8
|
|
10
|
-
|
11
|
-
require
|
9
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
10
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
11
|
+
|
12
|
+
require 'rails/all'
|
13
|
+
require 'rails/engine/commands'
|
data/db/migrate/{20240805210911_create_analytic_views.rb → 20240805210911_create_analytic_events.rb}
RENAMED
@@ -1,20 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class
|
3
|
+
class CreateAnalyticEvents < ActiveRecord::Migration[7.1]
|
4
4
|
def change
|
5
|
-
create_table :
|
5
|
+
create_table :analytic_events do |t|
|
6
6
|
t.uuid :visitor_id, null: false
|
7
7
|
t.uuid :session_id, null: false
|
8
|
+
t.inet :ip, null: false
|
8
9
|
t.string :path, null: false
|
9
10
|
t.string :host, null: false
|
10
11
|
t.jsonb :params, null: false, default: {}
|
11
12
|
t.datetime :timestamp, null: false
|
13
|
+
t.text :referrer, null: true
|
14
|
+
t.text :user_agent, null: true
|
12
15
|
|
13
16
|
t.timestamps
|
14
17
|
|
15
18
|
t.index %i[timestamp path]
|
16
19
|
t.index %i[timestamp visitor_id]
|
17
20
|
t.index %i[timestamp session_id]
|
21
|
+
t.index %i[timestamp referrer]
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
data/lib/analytic/config.rb
CHANGED
@@ -2,11 +2,71 @@
|
|
2
2
|
|
3
3
|
module Analytic
|
4
4
|
class Config
|
5
|
-
# @
|
6
|
-
|
5
|
+
# @example
|
6
|
+
# config.time_zone = ActiveSupport::TimeZone['Tokyo']
|
7
|
+
#
|
8
|
+
# @!attribute [rw] time_zone
|
9
|
+
# @return [ActiveSupport::TimeZone]
|
10
|
+
attr_accessor :time_zone
|
11
|
+
|
12
|
+
# @example
|
13
|
+
# config.ip_v4_mask = 24
|
14
|
+
#
|
15
|
+
# @!attribute [rw] ip_v4_mask
|
16
|
+
# @return [Integer]
|
17
|
+
attr_accessor :ip_v4_mask
|
18
|
+
|
19
|
+
# @example
|
20
|
+
# config.ip_v6_mask = 48
|
21
|
+
#
|
22
|
+
# @!attribute [rw] ip_v6_mask
|
23
|
+
# @return [Integer]
|
24
|
+
attr_accessor :ip_v6_mask
|
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
|
7
43
|
|
8
44
|
def initialize
|
9
|
-
@
|
45
|
+
@time_zone = Time.zone
|
46
|
+
@ip_v4_mask = 24 # e.g. 255.255.255.255 => '255.255.255.0/255.255.255.0'
|
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]
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Boolean]
|
53
|
+
def ip_v4_mask?
|
54
|
+
@ip_v4_mask.present?
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Boolean]
|
58
|
+
def ip_v6_mask?
|
59
|
+
@ip_v6_mask.present?
|
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]
|
10
70
|
end
|
11
71
|
end
|
12
72
|
end
|
data/lib/analytic/engine.rb
CHANGED
data/lib/analytic/version.rb
CHANGED
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.
|
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-
|
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/
|
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/
|
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.
|
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: []
|