feat: add ahoy analytics for event tracking
- Integrate Ahoy gem for tracking user events and visits - Create models for Ahoy events and visits - Implement admin interfaces for managing events and visits - Add background job for cleaning up old analytics data - Update application controller and other relevant controllers to track specific actions This commit implements a comprehensive event tracking system that logs user interactions within the application. Additionally, it includes mechanisms for managing and cleaning historical visit and event data, ensuring efficient data handling.
This commit is contained in:
parent
5f30e08a6e
commit
dd6cd0451d
2
Gemfile
2
Gemfile
@ -50,6 +50,8 @@ gem "kaminari", "~> 1.2"
|
|||||||
gem "meta-tags", "~> 2.22"
|
gem "meta-tags", "~> 2.22"
|
||||||
gem "sitemap_generator", "~> 6.3"
|
gem "sitemap_generator", "~> 6.3"
|
||||||
|
|
||||||
|
gem "ahoy_matey", "~> 5.2"
|
||||||
|
|
||||||
# gem "whenever", "~> 1.0"
|
# gem "whenever", "~> 1.0"
|
||||||
gem "ruby-openai", "~> 7.3"
|
gem "ruby-openai", "~> 7.3"
|
||||||
gem "httparty", "~> 0.22.0"
|
gem "httparty", "~> 0.22.0"
|
||||||
|
@ -84,6 +84,10 @@ GEM
|
|||||||
uri (>= 0.13.1)
|
uri (>= 0.13.1)
|
||||||
addressable (2.8.7)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
|
ahoy_matey (5.2.1)
|
||||||
|
activesupport (>= 6.1)
|
||||||
|
device_detector (>= 1)
|
||||||
|
safely_block (>= 0.4)
|
||||||
arbre (1.7.0)
|
arbre (1.7.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
ruby2_keywords (>= 0.0.2)
|
ruby2_keywords (>= 0.0.2)
|
||||||
@ -136,6 +140,7 @@ GEM
|
|||||||
debug (1.10.0)
|
debug (1.10.0)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
|
device_detector (1.1.3)
|
||||||
devise (4.9.4)
|
devise (4.9.4)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
@ -385,6 +390,7 @@ GEM
|
|||||||
rubyzip (2.4.1)
|
rubyzip (2.4.1)
|
||||||
rufus-scheduler (3.9.2)
|
rufus-scheduler (3.9.2)
|
||||||
fugit (~> 1.1, >= 1.11.1)
|
fugit (~> 1.1, >= 1.11.1)
|
||||||
|
safely_block (0.4.1)
|
||||||
securerandom (0.4.1)
|
securerandom (0.4.1)
|
||||||
selenium-webdriver (4.28.0)
|
selenium-webdriver (4.28.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
@ -485,6 +491,7 @@ PLATFORMS
|
|||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
activeadmin (~> 3.2)
|
activeadmin (~> 3.2)
|
||||||
|
ahoy_matey (~> 5.2)
|
||||||
aws-sdk-s3 (~> 1.177)
|
aws-sdk-s3 (~> 1.177)
|
||||||
bootsnap
|
bootsnap
|
||||||
brakeman
|
brakeman
|
||||||
|
33
app/admin/ahoy_events.rb
Normal file
33
app/admin/ahoy_events.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
ActiveAdmin.register Ahoy::Event do
|
||||||
|
|
||||||
|
# See permitted parameters documentation:
|
||||||
|
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
|
||||||
|
#
|
||||||
|
# Uncomment all parameters which should be permitted for assignment
|
||||||
|
#
|
||||||
|
# permit_params :visit_id, :user_id, :name, :properties, :time
|
||||||
|
#
|
||||||
|
# or
|
||||||
|
#
|
||||||
|
# permit_params do
|
||||||
|
# permitted = [:visit_id, :user_id, :name, :properties, :time]
|
||||||
|
# permitted << :other if params[:action] == 'create' && current_user.admin?
|
||||||
|
# permitted
|
||||||
|
# end
|
||||||
|
menu priority: 101, label: "事件统计"
|
||||||
|
|
||||||
|
actions :index
|
||||||
|
|
||||||
|
index do
|
||||||
|
column :id
|
||||||
|
column :name
|
||||||
|
column :time
|
||||||
|
column :properties
|
||||||
|
column :user_id
|
||||||
|
end
|
||||||
|
|
||||||
|
filter :name
|
||||||
|
filter :time
|
||||||
|
filter :properties
|
||||||
|
|
||||||
|
end
|
34
app/admin/ahoy_management.rb
Normal file
34
app/admin/ahoy_management.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# app/admin/ahoy_management.rb
|
||||||
|
ActiveAdmin.register_page "Ahoy Management" do
|
||||||
|
menu label: "访问数据管理", parent: "系统管理"
|
||||||
|
|
||||||
|
content title: "访问数据管理" do
|
||||||
|
columns do
|
||||||
|
column do
|
||||||
|
panel "数据统计" do
|
||||||
|
attributes_table_for :ahoy do
|
||||||
|
row("总事件数") { Ahoy::Event.count }
|
||||||
|
row("总访问数") { Ahoy::Visit.count }
|
||||||
|
row("最早事件") { Ahoy::Event.minimum(:time)&.strftime("%Y-%m-%d %H:%M:%S") }
|
||||||
|
row("最早访问") { Ahoy::Visit.minimum(:started_at)&.strftime("%Y-%m-%d %H:%M:%S") }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
column do
|
||||||
|
panel "操作" do
|
||||||
|
div class: "buttons" do
|
||||||
|
button_to "立即清理旧数据", admin_ahoy_management_cleanup_path, method: :post,
|
||||||
|
data: { confirm: "确定要清理3个月前的数据吗?" },
|
||||||
|
class: "button"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
page_action :cleanup, method: :post do
|
||||||
|
CleanAhoyDataWorker.perform_async
|
||||||
|
redirect_to admin_ahoy_management_path, notice: "清理任务已加入队列"
|
||||||
|
end
|
||||||
|
end
|
37
app/admin/ahoy_visits.rb
Normal file
37
app/admin/ahoy_visits.rb
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
ActiveAdmin.register Ahoy::Visit do
|
||||||
|
|
||||||
|
# See permitted parameters documentation:
|
||||||
|
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
|
||||||
|
#
|
||||||
|
# Uncomment all parameters which should be permitted for assignment
|
||||||
|
#
|
||||||
|
# permit_params :visit_token, :visitor_token, :user_id, :ip, :user_agent, :referrer, :referring_domain, :landing_page, :browser, :os, :device_type, :country, :region, :city, :latitude, :longitude, :utm_source, :utm_medium, :utm_term, :utm_content, :utm_campaign, :app_version, :os_version, :platform, :started_at
|
||||||
|
#
|
||||||
|
# or
|
||||||
|
#
|
||||||
|
# permit_params do
|
||||||
|
# permitted = [:visit_token, :visitor_token, :user_id, :ip, :user_agent, :referrer, :referring_domain, :landing_page, :browser, :os, :device_type, :country, :region, :city, :latitude, :longitude, :utm_source, :utm_medium, :utm_term, :utm_content, :utm_campaign, :app_version, :os_version, :platform, :started_at]
|
||||||
|
# permitted << :other if params[:action] == 'create' && current_user.admin?
|
||||||
|
# permitted
|
||||||
|
# end
|
||||||
|
|
||||||
|
menu priority: 100, label: "访问统计"
|
||||||
|
|
||||||
|
actions :index
|
||||||
|
|
||||||
|
index do
|
||||||
|
column :id
|
||||||
|
column :visitor_token
|
||||||
|
column :ip
|
||||||
|
column :user_agent
|
||||||
|
column :started_at
|
||||||
|
column :city
|
||||||
|
column :country
|
||||||
|
column :region
|
||||||
|
end
|
||||||
|
|
||||||
|
filter :started_at
|
||||||
|
filter :city
|
||||||
|
filter :country
|
||||||
|
|
||||||
|
end
|
@ -11,6 +11,43 @@ ActiveAdmin.register_page "Dashboard" do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
columns do
|
||||||
|
column do
|
||||||
|
panel "访问统计" do
|
||||||
|
para "总访问量: #{Ahoy::Visit.count}"
|
||||||
|
para "总事件数: #{Ahoy::Event.count}"
|
||||||
|
para "独立访客数: #{Ahoy::Visit.distinct.count(:visitor_token)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
column do
|
||||||
|
panel "热门城市" do
|
||||||
|
table_for City.by_popularity.limit(10) do
|
||||||
|
column("城市") { |city| link_to(city.name, admin_city_path(city)) }
|
||||||
|
column("访问量") { |city| city.view_count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
column do
|
||||||
|
panel "热门天气艺术" do
|
||||||
|
table_for WeatherArt.by_popularity.limit(10) do
|
||||||
|
column("作品") { |art| link_to(art.to_s, admin_weather_art_path(art)) }
|
||||||
|
column("访问量") { |art| art.view_count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# 添加一个事件列表面板
|
||||||
|
panel "最近事件" do
|
||||||
|
table_for Ahoy::Event.order(time: :desc).limit(10) do
|
||||||
|
column :time
|
||||||
|
column :name
|
||||||
|
column :properties
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Here is an example of a simple dashboard with columns and panels.
|
# Here is an example of a simple dashboard with columns and panels.
|
||||||
#
|
#
|
||||||
# columns do
|
# columns do
|
||||||
|
@ -3,6 +3,13 @@ class ApplicationController < ActionController::Base
|
|||||||
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
|
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
|
||||||
allow_browser versions: :modern
|
allow_browser versions: :modern
|
||||||
before_action :set_locale
|
before_action :set_locale
|
||||||
|
after_action :track_action
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def track_action
|
||||||
|
ahoy.track "Viewed Application", request.path_parameters
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
@ -24,6 +24,11 @@ class CitiesController < ApplicationController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@city = City.friendly.find(params[:id])
|
@city = City.friendly.find(params[:id])
|
||||||
|
ahoy.track "View City", {
|
||||||
|
city_id: @city.id,
|
||||||
|
name: @city.name,
|
||||||
|
event_type: 'city_view'
|
||||||
|
}
|
||||||
|
|
||||||
set_meta_tags(
|
set_meta_tags(
|
||||||
title: @city.name,
|
title: @city.name,
|
||||||
|
@ -2,6 +2,18 @@ class WeatherArtsController < ApplicationController
|
|||||||
def show
|
def show
|
||||||
@city = City.friendly.find(params[:city_id])
|
@city = City.friendly.find(params[:city_id])
|
||||||
@weather_art = @city.weather_arts.friendly.find(params[:slug])
|
@weather_art = @city.weather_arts.friendly.find(params[:slug])
|
||||||
|
|
||||||
|
ahoy.track "View Weather Art", {
|
||||||
|
weather_art_id: @weather_art.id,
|
||||||
|
city_id: @weather_art.city_id,
|
||||||
|
event_type: 'weather_art_view'
|
||||||
|
}
|
||||||
|
ahoy.track "View City", {
|
||||||
|
city_id: @city.id,
|
||||||
|
name: @city.name,
|
||||||
|
event_type: 'city_view'
|
||||||
|
}
|
||||||
|
|
||||||
set_meta_tags(
|
set_meta_tags(
|
||||||
title: "#{@city.name} Weather Art - #{@weather_art.weather_date.strftime('%B %d, %Y')}",
|
title: "#{@city.name} Weather Art - #{@weather_art.weather_date.strftime('%B %d, %Y')}",
|
||||||
description: "#{@city.name}'s weather visualized through AI art. #{@weather_art.description} at #{@weather_art.temperature}°C.",
|
description: "#{@city.name}'s weather visualized through AI art. #{@weather_art.description} at #{@weather_art.temperature}°C.",
|
||||||
|
14
app/models/ahoy/event.rb
Normal file
14
app/models/ahoy/event.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class Ahoy::Event < ApplicationRecord
|
||||||
|
# include Ahoy::QueryMethods
|
||||||
|
|
||||||
|
self.table_name = "ahoy_events"
|
||||||
|
|
||||||
|
belongs_to :visit
|
||||||
|
belongs_to :user, optional: true
|
||||||
|
|
||||||
|
serialize :properties, coder: JSON
|
||||||
|
|
||||||
|
def self.ransackable_attributes(auth_object = nil)
|
||||||
|
["id", "id_value", "name", "properties", "time", "user_id", "visit_id"]
|
||||||
|
end
|
||||||
|
end
|
10
app/models/ahoy/visit.rb
Normal file
10
app/models/ahoy/visit.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class Ahoy::Visit < ApplicationRecord
|
||||||
|
self.table_name = "ahoy_visits"
|
||||||
|
|
||||||
|
has_many :events, class_name: "Ahoy::Event"
|
||||||
|
belongs_to :user, optional: true
|
||||||
|
|
||||||
|
def self.ransackable_attributes(auth_object = nil)
|
||||||
|
["app_version", "browser", "city", "country", "device_type", "id", "ip", "landing_page", "latitude", "longitude", "os", "os_version", "platform", "referrer", "referring_domain", "region", "started_at", "user_agent", "user_id", "utm_campaign", "utm_content", "utm_medium", "utm_source", "utm_term", "visit_token", "visitor_token"]
|
||||||
|
end
|
||||||
|
end
|
@ -5,6 +5,9 @@ class City < ApplicationRecord
|
|||||||
|
|
||||||
has_many :weather_arts, dependent: :destroy
|
has_many :weather_arts, dependent: :destroy
|
||||||
|
|
||||||
|
has_many :visits, class_name: "Ahoy::Visit", foreign_key: :city_id
|
||||||
|
has_many :events, class_name: "Ahoy::Event", foreign_key: :city_id
|
||||||
|
|
||||||
delegate :region, to: :country
|
delegate :region, to: :country
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
@ -17,6 +20,23 @@ class City < ApplicationRecord
|
|||||||
scope :by_country, ->(country_id) { where(country_id: country_id) }
|
scope :by_country, ->(country_id) { where(country_id: country_id) }
|
||||||
scope :active, -> { where(active: true) }
|
scope :active, -> { where(active: true) }
|
||||||
|
|
||||||
|
|
||||||
|
scope :by_popularity, -> {
|
||||||
|
if ActiveRecord::Base.connection.adapter_name.downcase == 'sqlite'
|
||||||
|
joins("LEFT JOIN ahoy_events ON json_extract(ahoy_events.properties, '$.city_id') = cities.id
|
||||||
|
AND json_extract(ahoy_events.properties, '$.event_type') = 'city_view'")
|
||||||
|
.group("cities.id")
|
||||||
|
.select("cities.*, COUNT(ahoy_events.id) as visit_count")
|
||||||
|
.order("visit_count DESC")
|
||||||
|
else
|
||||||
|
joins("LEFT JOIN ahoy_events ON (ahoy_events.properties->>'city_id')::integer = cities.id
|
||||||
|
AND ahoy_events.properties->>'event_type' = 'city_view'")
|
||||||
|
.group("cities.id")
|
||||||
|
.select("cities.*, COUNT(ahoy_events.id) as visit_count")
|
||||||
|
.order("visit_count DESC")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
name
|
name
|
||||||
end
|
end
|
||||||
@ -65,4 +85,12 @@ class City < ApplicationRecord
|
|||||||
def latest_weather_art
|
def latest_weather_art
|
||||||
weather_arts.order(weather_date: :desc).first
|
weather_arts.order(weather_date: :desc).first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def view_count
|
||||||
|
if ActiveRecord::Base.connection.adapter_name.downcase == 'sqlite'
|
||||||
|
Ahoy::Event.where("json_extract(properties, '$.event_type') = 'city_view' AND json_extract(properties, '$.city_id') = ?", self.id).count
|
||||||
|
else
|
||||||
|
Ahoy::Event.where("properties->>'event_type' = 'city_view' AND (properties->>'city_id')::integer = ?", self.id).count
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -5,9 +5,28 @@ class WeatherArt < ApplicationRecord
|
|||||||
belongs_to :city
|
belongs_to :city
|
||||||
has_one_attached :image
|
has_one_attached :image
|
||||||
|
|
||||||
|
has_many :visits, class_name: "Ahoy::Visit", foreign_key: :weather_art_id
|
||||||
|
has_many :events, class_name: "Ahoy::Event", foreign_key: :weather_art_id
|
||||||
|
|
||||||
validates :weather_date, presence: true
|
validates :weather_date, presence: true
|
||||||
validates :city_id, presence: true
|
validates :city_id, presence: true
|
||||||
|
|
||||||
|
scope :by_popularity, -> {
|
||||||
|
if ActiveRecord::Base.connection.adapter_name.downcase == 'sqlite'
|
||||||
|
joins("LEFT JOIN ahoy_events ON json_extract(ahoy_events.properties, '$.weather_art_id') = weather_arts.id
|
||||||
|
AND json_extract(ahoy_events.properties, '$.event_type') = 'weather_art_view'")
|
||||||
|
.group("weather_arts.id")
|
||||||
|
.select("weather_arts.*, COUNT(ahoy_events.id) as visit_count")
|
||||||
|
.order("visit_count DESC")
|
||||||
|
else
|
||||||
|
joins("LEFT JOIN ahoy_events ON (ahoy_events.properties->>'weather_art_id')::integer = weather_arts.id
|
||||||
|
AND ahoy_events.properties->>'event_type' = 'weather_art_view'")
|
||||||
|
.group("weather_arts.id")
|
||||||
|
.select("weather_arts.*, COUNT(ahoy_events.id) as visit_count")
|
||||||
|
.order("visit_count DESC")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
def should_generate_new_friendly_id?
|
def should_generate_new_friendly_id?
|
||||||
weather_date_changed? || city_id_changed? || super
|
weather_date_changed? || city_id_changed? || super
|
||||||
end
|
end
|
||||||
@ -23,4 +42,12 @@ class WeatherArt < ApplicationRecord
|
|||||||
def self.ransackable_attributes(auth_object = nil)
|
def self.ransackable_attributes(auth_object = nil)
|
||||||
[ "city_id", "cloud", "created_at", "description", "feeling_temp", "humidity", "id", "id_value", "precipitation", "pressure", "prompt", "temperature", "updated_at", "visibility", "weather_date", "wind_scale", "wind_speed" ]
|
[ "city_id", "cloud", "created_at", "description", "feeling_temp", "humidity", "id", "id_value", "precipitation", "pressure", "prompt", "temperature", "updated_at", "visibility", "weather_date", "wind_scale", "wind_speed" ]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def view_count
|
||||||
|
if ActiveRecord::Base.connection.adapter_name.downcase == 'sqlite'
|
||||||
|
Ahoy::Event.where("json_extract(properties, '$.event_type') = 'weather_art_view' AND json_extract(properties, '$.weather_art_id') = ?", self.id).count
|
||||||
|
else
|
||||||
|
Ahoy::Event.where("properties->>'event_type' = 'weather_art_view' AND (properties->>'weather_art_id')::integer = ?", self.id).count
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
32
app/workers/clean_ahoy_data_worker.rb
Normal file
32
app/workers/clean_ahoy_data_worker.rb
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# app/workers/clean_ahoy_data_worker.rb
|
||||||
|
class CleanAhoyDataWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: :default, retry: false
|
||||||
|
|
||||||
|
def perform
|
||||||
|
cleanup_old_events
|
||||||
|
cleanup_old_visits
|
||||||
|
log_cleanup_results
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def cleanup_old_events
|
||||||
|
cutoff_date = 3.months.ago
|
||||||
|
deleted_events_count = Ahoy::Event.where('time < ?', cutoff_date).delete_all
|
||||||
|
Rails.logger.info "Deleted #{deleted_events_count} old Ahoy events"
|
||||||
|
end
|
||||||
|
|
||||||
|
def cleanup_old_visits
|
||||||
|
cutoff_date = 3.months.ago
|
||||||
|
deleted_visits_count = Ahoy::Visit.where('started_at < ?', cutoff_date).delete_all
|
||||||
|
Rails.logger.info "Deleted #{deleted_visits_count} old Ahoy visits"
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_cleanup_results
|
||||||
|
Rails.logger.info "Ahoy cleanup completed at #{Time.current}"
|
||||||
|
Rails.logger.info "Remaining events: #{Ahoy::Event.count}"
|
||||||
|
Rails.logger.info "Remaining visits: #{Ahoy::Visit.count}"
|
||||||
|
end
|
||||||
|
end
|
14
config/initializers/ahoy.rb
Normal file
14
config/initializers/ahoy.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class Ahoy::Store < Ahoy::DatabaseStore
|
||||||
|
end
|
||||||
|
|
||||||
|
# set to true for JavaScript tracking
|
||||||
|
Ahoy.api = true
|
||||||
|
|
||||||
|
# set to true for geocoding (and add the geocoder gem to your Gemfile)
|
||||||
|
# we recommend configuring local geocoding as well
|
||||||
|
# see https://github.com/ankane/ahoy#geocoding
|
||||||
|
Ahoy.geocode = false
|
||||||
|
|
||||||
|
Ahoy.visit_duration = 30.minutes
|
||||||
|
Ahoy.server_side_visits = :when_needed
|
||||||
|
RETENTION_PERIOD = 3.months
|
@ -9,4 +9,10 @@ refresh_sitemap:
|
|||||||
class: RefreshSitemapWorker
|
class: RefreshSitemapWorker
|
||||||
queue: default
|
queue: default
|
||||||
description: "Refresh sitemap daily"
|
description: "Refresh sitemap daily"
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
clean_ahoy_data:
|
||||||
|
cron: '0 0 * * 0'
|
||||||
|
class: CleanAhoyDataWorker
|
||||||
|
queue: default
|
||||||
enabled: true
|
enabled: true
|
61
db/migrate/20250126155239_create_ahoy_visits_and_events.rb
Normal file
61
db/migrate/20250126155239_create_ahoy_visits_and_events.rb
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
class CreateAhoyVisitsAndEvents < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
create_table :ahoy_visits do |t|
|
||||||
|
t.string :visit_token
|
||||||
|
t.string :visitor_token
|
||||||
|
|
||||||
|
# the rest are recommended but optional
|
||||||
|
# simply remove any you don't want
|
||||||
|
|
||||||
|
# user
|
||||||
|
t.references :user
|
||||||
|
|
||||||
|
# standard
|
||||||
|
t.string :ip
|
||||||
|
t.text :user_agent
|
||||||
|
t.text :referrer
|
||||||
|
t.string :referring_domain
|
||||||
|
t.text :landing_page
|
||||||
|
|
||||||
|
# technology
|
||||||
|
t.string :browser
|
||||||
|
t.string :os
|
||||||
|
t.string :device_type
|
||||||
|
|
||||||
|
# location
|
||||||
|
t.string :country
|
||||||
|
t.string :region
|
||||||
|
t.string :city
|
||||||
|
t.float :latitude
|
||||||
|
t.float :longitude
|
||||||
|
|
||||||
|
# utm parameters
|
||||||
|
t.string :utm_source
|
||||||
|
t.string :utm_medium
|
||||||
|
t.string :utm_term
|
||||||
|
t.string :utm_content
|
||||||
|
t.string :utm_campaign
|
||||||
|
|
||||||
|
# native apps
|
||||||
|
t.string :app_version
|
||||||
|
t.string :os_version
|
||||||
|
t.string :platform
|
||||||
|
|
||||||
|
t.datetime :started_at
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :ahoy_visits, :visit_token, unique: true
|
||||||
|
add_index :ahoy_visits, [:visitor_token, :started_at]
|
||||||
|
|
||||||
|
create_table :ahoy_events do |t|
|
||||||
|
t.references :visit
|
||||||
|
t.references :user
|
||||||
|
|
||||||
|
t.string :name
|
||||||
|
t.text :properties
|
||||||
|
t.datetime :time
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :ahoy_events, [:name, :time]
|
||||||
|
end
|
||||||
|
end
|
44
db/schema.rb
generated
44
db/schema.rb
generated
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_01_23_155234) do
|
ActiveRecord::Schema[8.0].define(version: 2025_01_26_155239) do
|
||||||
create_table "active_admin_comments", force: :cascade do |t|
|
create_table "active_admin_comments", force: :cascade do |t|
|
||||||
t.string "namespace"
|
t.string "namespace"
|
||||||
t.text "body"
|
t.text "body"
|
||||||
@ -65,6 +65,48 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_23_155234) do
|
|||||||
t.index ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true
|
t.index ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "ahoy_events", force: :cascade do |t|
|
||||||
|
t.integer "visit_id"
|
||||||
|
t.integer "user_id"
|
||||||
|
t.string "name"
|
||||||
|
t.text "properties"
|
||||||
|
t.datetime "time"
|
||||||
|
t.index ["name", "time"], name: "index_ahoy_events_on_name_and_time"
|
||||||
|
t.index ["user_id"], name: "index_ahoy_events_on_user_id"
|
||||||
|
t.index ["visit_id"], name: "index_ahoy_events_on_visit_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "ahoy_visits", force: :cascade do |t|
|
||||||
|
t.string "visit_token"
|
||||||
|
t.string "visitor_token"
|
||||||
|
t.integer "user_id"
|
||||||
|
t.string "ip"
|
||||||
|
t.text "user_agent"
|
||||||
|
t.text "referrer"
|
||||||
|
t.string "referring_domain"
|
||||||
|
t.text "landing_page"
|
||||||
|
t.string "browser"
|
||||||
|
t.string "os"
|
||||||
|
t.string "device_type"
|
||||||
|
t.string "country"
|
||||||
|
t.string "region"
|
||||||
|
t.string "city"
|
||||||
|
t.float "latitude"
|
||||||
|
t.float "longitude"
|
||||||
|
t.string "utm_source"
|
||||||
|
t.string "utm_medium"
|
||||||
|
t.string "utm_term"
|
||||||
|
t.string "utm_content"
|
||||||
|
t.string "utm_campaign"
|
||||||
|
t.string "app_version"
|
||||||
|
t.string "os_version"
|
||||||
|
t.string "platform"
|
||||||
|
t.datetime "started_at"
|
||||||
|
t.index ["user_id"], name: "index_ahoy_visits_on_user_id"
|
||||||
|
t.index ["visit_token"], name: "index_ahoy_visits_on_visit_token", unique: true
|
||||||
|
t.index ["visitor_token", "started_at"], name: "index_ahoy_visits_on_visitor_token_and_started_at"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "cities", force: :cascade do |t|
|
create_table "cities", force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name"
|
||||||
t.float "latitude"
|
t.float "latitude"
|
||||||
|
Loading…
Reference in New Issue
Block a user