- Track user sessions, page views, clicks - Measure engagement score and session duration - Device breakdown (desktop/mobile/tablet) - User rankings by activity - Popular pages statistics - Recent sessions feed - SQL migration for analytics tables - JavaScript tracker for frontend events Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
251 lines
8.5 KiB
PL/PgSQL
251 lines
8.5 KiB
PL/PgSQL
-- Migration: 014_user_analytics.sql
|
|
-- Description: User activity tracking and analytics
|
|
-- Created: 2026-01-13
|
|
-- Author: Claude
|
|
|
|
-- ============================================================
|
|
-- TABELE ANALITYKI UŻYTKOWNIKÓW
|
|
-- ============================================================
|
|
|
|
-- Sesje użytkowników
|
|
CREATE TABLE IF NOT EXISTS user_sessions (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
|
session_id VARCHAR(100) UNIQUE NOT NULL,
|
|
|
|
-- Czas sesji
|
|
started_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
ended_at TIMESTAMP,
|
|
last_activity_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
duration_seconds INTEGER,
|
|
|
|
-- Urządzenie
|
|
ip_address VARCHAR(45),
|
|
user_agent TEXT,
|
|
device_type VARCHAR(20), -- desktop, mobile, tablet
|
|
browser VARCHAR(50),
|
|
browser_version VARCHAR(20),
|
|
os VARCHAR(50),
|
|
os_version VARCHAR(20),
|
|
|
|
-- Lokalizacja (z IP)
|
|
country VARCHAR(100),
|
|
city VARCHAR(100),
|
|
region VARCHAR(100),
|
|
|
|
-- Metryki sesji
|
|
page_views_count INTEGER DEFAULT 0,
|
|
clicks_count INTEGER DEFAULT 0,
|
|
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_user_sessions_user ON user_sessions(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_user_sessions_started ON user_sessions(started_at);
|
|
CREATE INDEX IF NOT EXISTS idx_user_sessions_session_id ON user_sessions(session_id);
|
|
|
|
-- Wyświetlenia stron
|
|
CREATE TABLE IF NOT EXISTS page_views (
|
|
id SERIAL PRIMARY KEY,
|
|
session_id INTEGER REFERENCES user_sessions(id) ON DELETE CASCADE,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
|
|
-- Strona
|
|
url VARCHAR(2000) NOT NULL,
|
|
path VARCHAR(500) NOT NULL,
|
|
page_title VARCHAR(500),
|
|
referrer VARCHAR(2000),
|
|
|
|
-- Czas
|
|
viewed_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
time_on_page_seconds INTEGER,
|
|
|
|
-- Kontekst
|
|
company_id INTEGER REFERENCES companies(id) ON DELETE SET NULL,
|
|
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_page_views_session ON page_views(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_page_views_user ON page_views(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_page_views_path ON page_views(path);
|
|
CREATE INDEX IF NOT EXISTS idx_page_views_viewed ON page_views(viewed_at);
|
|
CREATE INDEX IF NOT EXISTS idx_page_views_company ON page_views(company_id);
|
|
|
|
-- Kliknięcia użytkowników
|
|
CREATE TABLE IF NOT EXISTS user_clicks (
|
|
id SERIAL PRIMARY KEY,
|
|
session_id INTEGER REFERENCES user_sessions(id) ON DELETE CASCADE,
|
|
page_view_id INTEGER REFERENCES page_views(id) ON DELETE CASCADE,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
|
|
-- Element kliknięty
|
|
element_type VARCHAR(50), -- button, link, card, nav, form
|
|
element_id VARCHAR(100),
|
|
element_text VARCHAR(255),
|
|
element_class VARCHAR(500),
|
|
target_url VARCHAR(2000),
|
|
|
|
-- Pozycja
|
|
x_position INTEGER,
|
|
y_position INTEGER,
|
|
|
|
clicked_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_user_clicks_session ON user_clicks(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_user_clicks_element ON user_clicks(element_type);
|
|
CREATE INDEX IF NOT EXISTS idx_user_clicks_clicked ON user_clicks(clicked_at);
|
|
|
|
-- Dzienne statystyki (agregowane)
|
|
CREATE TABLE IF NOT EXISTS analytics_daily (
|
|
id SERIAL PRIMARY KEY,
|
|
date DATE UNIQUE NOT NULL,
|
|
|
|
-- Sesje
|
|
total_sessions INTEGER DEFAULT 0,
|
|
unique_users INTEGER DEFAULT 0,
|
|
new_users INTEGER DEFAULT 0,
|
|
returning_users INTEGER DEFAULT 0,
|
|
anonymous_sessions INTEGER DEFAULT 0,
|
|
|
|
-- Aktywność
|
|
total_page_views INTEGER DEFAULT 0,
|
|
total_clicks INTEGER DEFAULT 0,
|
|
avg_session_duration_seconds INTEGER,
|
|
avg_pages_per_session NUMERIC(5,2),
|
|
|
|
-- Urządzenia
|
|
desktop_sessions INTEGER DEFAULT 0,
|
|
mobile_sessions INTEGER DEFAULT 0,
|
|
tablet_sessions INTEGER DEFAULT 0,
|
|
|
|
-- Engagement
|
|
bounce_rate NUMERIC(5,2), -- % sesji z 1 page view
|
|
|
|
updated_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_analytics_daily_date ON analytics_daily(date);
|
|
|
|
-- Popularne strony (dzienne)
|
|
CREATE TABLE IF NOT EXISTS popular_pages_daily (
|
|
id SERIAL PRIMARY KEY,
|
|
date DATE NOT NULL,
|
|
path VARCHAR(500) NOT NULL,
|
|
page_title VARCHAR(500),
|
|
|
|
views_count INTEGER DEFAULT 0,
|
|
unique_visitors INTEGER DEFAULT 0,
|
|
avg_time_seconds INTEGER,
|
|
|
|
UNIQUE(date, path)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_popular_pages_date ON popular_pages_daily(date);
|
|
|
|
-- ============================================================
|
|
-- GRANT PERMISSIONS
|
|
-- ============================================================
|
|
GRANT ALL ON TABLE user_sessions TO nordabiz_app;
|
|
GRANT ALL ON TABLE page_views TO nordabiz_app;
|
|
GRANT ALL ON TABLE user_clicks TO nordabiz_app;
|
|
GRANT ALL ON TABLE analytics_daily TO nordabiz_app;
|
|
GRANT ALL ON TABLE popular_pages_daily TO nordabiz_app;
|
|
|
|
GRANT USAGE, SELECT ON SEQUENCE user_sessions_id_seq TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON SEQUENCE page_views_id_seq TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON SEQUENCE user_clicks_id_seq TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON SEQUENCE analytics_daily_id_seq TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON SEQUENCE popular_pages_daily_id_seq TO nordabiz_app;
|
|
|
|
-- ============================================================
|
|
-- TRIGGER: Update analytics_daily on new session/page_view
|
|
-- ============================================================
|
|
|
|
-- Function to update daily analytics
|
|
CREATE OR REPLACE FUNCTION update_analytics_daily()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
target_date DATE;
|
|
BEGIN
|
|
IF TG_TABLE_NAME = 'user_sessions' THEN
|
|
target_date := DATE(NEW.started_at);
|
|
ELSIF TG_TABLE_NAME = 'page_views' THEN
|
|
target_date := DATE(NEW.viewed_at);
|
|
ELSE
|
|
RETURN NEW;
|
|
END IF;
|
|
|
|
-- Insert or update daily stats
|
|
INSERT INTO analytics_daily (date, total_sessions, total_page_views, updated_at)
|
|
VALUES (target_date, 0, 0, NOW())
|
|
ON CONFLICT (date) DO NOTHING;
|
|
|
|
-- Update counts based on trigger source
|
|
IF TG_TABLE_NAME = 'user_sessions' THEN
|
|
UPDATE analytics_daily SET
|
|
total_sessions = total_sessions + 1,
|
|
unique_users = (
|
|
SELECT COUNT(DISTINCT user_id)
|
|
FROM user_sessions
|
|
WHERE DATE(started_at) = target_date AND user_id IS NOT NULL
|
|
),
|
|
anonymous_sessions = (
|
|
SELECT COUNT(*)
|
|
FROM user_sessions
|
|
WHERE DATE(started_at) = target_date AND user_id IS NULL
|
|
),
|
|
desktop_sessions = (
|
|
SELECT COUNT(*) FROM user_sessions
|
|
WHERE DATE(started_at) = target_date AND device_type = 'desktop'
|
|
),
|
|
mobile_sessions = (
|
|
SELECT COUNT(*) FROM user_sessions
|
|
WHERE DATE(started_at) = target_date AND device_type = 'mobile'
|
|
),
|
|
tablet_sessions = (
|
|
SELECT COUNT(*) FROM user_sessions
|
|
WHERE DATE(started_at) = target_date AND device_type = 'tablet'
|
|
),
|
|
updated_at = NOW()
|
|
WHERE date = target_date;
|
|
|
|
ELSIF TG_TABLE_NAME = 'page_views' THEN
|
|
UPDATE analytics_daily SET
|
|
total_page_views = total_page_views + 1,
|
|
updated_at = NOW()
|
|
WHERE date = target_date;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Create triggers
|
|
DROP TRIGGER IF EXISTS trg_update_analytics_on_session ON user_sessions;
|
|
CREATE TRIGGER trg_update_analytics_on_session
|
|
AFTER INSERT ON user_sessions
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_analytics_daily();
|
|
|
|
DROP TRIGGER IF EXISTS trg_update_analytics_on_pageview ON page_views;
|
|
CREATE TRIGGER trg_update_analytics_on_pageview
|
|
AFTER INSERT ON page_views
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_analytics_daily();
|
|
|
|
-- ============================================================
|
|
-- COMMENTS
|
|
-- ============================================================
|
|
COMMENT ON TABLE user_sessions IS 'Sesje użytkowników portalu NordaBiznes';
|
|
COMMENT ON TABLE page_views IS 'Wyświetlenia stron przez użytkowników';
|
|
COMMENT ON TABLE user_clicks IS 'Kliknięcia elementów na stronach';
|
|
COMMENT ON TABLE analytics_daily IS 'Dzienne statystyki agregowane';
|
|
COMMENT ON TABLE popular_pages_daily IS 'Popularne strony - dzienne agregaty';
|
|
|
|
COMMENT ON COLUMN user_sessions.device_type IS 'Typ urządzenia: desktop, mobile, tablet';
|
|
COMMENT ON COLUMN user_sessions.duration_seconds IS 'Czas trwania sesji w sekundach';
|
|
COMMENT ON COLUMN page_views.time_on_page_seconds IS 'Czas spędzony na stronie w sekundach';
|
|
COMMENT ON COLUMN analytics_daily.bounce_rate IS 'Procent sesji z tylko 1 wyświetleniem strony';
|