Nowe funkcjonalności: - GeoIP enrichment (kraj, miasto, region) - UTM parameters tracking (source, medium, campaign, term, content) - Bounce rate calculation - Search queries logging - Conversion tracking (register, login, contact_click, rsvp) - Scroll depth tracking (25%, 50%, 75%, 100%) - JS error tracking (window.onerror) - Performance metrics (Web Vitals) - CSV export (sessions, pageviews, searches, conversions) Nowe tabele SQL: - search_queries - conversion_events - js_errors - popular_searches_daily - hourly_activity Dashboard rozszerzony o nowe sekcje i metryki. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
265 lines
12 KiB
PL/PgSQL
265 lines
12 KiB
PL/PgSQL
-- ============================================================
|
|
-- Migration: 033_analytics_expansion.sql
|
|
-- Description: Rozbudowa systemu analityki użytkowników
|
|
-- Author: Claude
|
|
-- Date: 2026-01-30
|
|
-- ============================================================
|
|
|
|
BEGIN;
|
|
|
|
-- ============================================================
|
|
-- 1. UTM PARAMETERS - Dodanie do user_sessions
|
|
-- ============================================================
|
|
|
|
ALTER TABLE user_sessions ADD COLUMN IF NOT EXISTS utm_source VARCHAR(255);
|
|
ALTER TABLE user_sessions ADD COLUMN IF NOT EXISTS utm_medium VARCHAR(255);
|
|
ALTER TABLE user_sessions ADD COLUMN IF NOT EXISTS utm_campaign VARCHAR(255);
|
|
ALTER TABLE user_sessions ADD COLUMN IF NOT EXISTS utm_term VARCHAR(255);
|
|
ALTER TABLE user_sessions ADD COLUMN IF NOT EXISTS utm_content VARCHAR(255);
|
|
|
|
-- Indeks dla raportowania kampanii
|
|
CREATE INDEX IF NOT EXISTS idx_user_sessions_utm_source ON user_sessions(utm_source) WHERE utm_source IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_user_sessions_utm_campaign ON user_sessions(utm_campaign) WHERE utm_campaign IS NOT NULL;
|
|
|
|
COMMENT ON COLUMN user_sessions.utm_source IS 'Źródło ruchu: google, facebook, newsletter';
|
|
COMMENT ON COLUMN user_sessions.utm_medium IS 'Medium: cpc, email, social, organic';
|
|
COMMENT ON COLUMN user_sessions.utm_campaign IS 'Nazwa kampanii marketingowej';
|
|
COMMENT ON COLUMN user_sessions.utm_term IS 'Słowo kluczowe (dla reklam PPC)';
|
|
COMMENT ON COLUMN user_sessions.utm_content IS 'Wariant reklamy/linku';
|
|
|
|
-- ============================================================
|
|
-- 2. SCROLL DEPTH - Dodanie do page_views
|
|
-- ============================================================
|
|
|
|
ALTER TABLE page_views ADD COLUMN IF NOT EXISTS scroll_depth_percent INTEGER;
|
|
|
|
COMMENT ON COLUMN page_views.scroll_depth_percent IS 'Maksymalny % przewinięcia strony (0-100)';
|
|
|
|
-- ============================================================
|
|
-- 3. SEARCH QUERIES - Nowa tabela
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS search_queries (
|
|
id SERIAL PRIMARY KEY,
|
|
session_id INTEGER REFERENCES user_sessions(id) ON DELETE SET NULL,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
|
|
-- Zapytanie
|
|
query VARCHAR(500) NOT NULL,
|
|
query_normalized VARCHAR(500), -- lowercase, bez znaków specjalnych
|
|
|
|
-- Wyniki
|
|
results_count INTEGER DEFAULT 0,
|
|
has_results BOOLEAN DEFAULT TRUE,
|
|
|
|
-- Interakcja z wynikami
|
|
clicked_result_position INTEGER, -- Która pozycja była kliknięta (1-based)
|
|
clicked_company_id INTEGER REFERENCES companies(id) ON DELETE SET NULL,
|
|
|
|
-- Kontekst
|
|
search_type VARCHAR(50) DEFAULT 'main', -- main, chat, autocomplete
|
|
filters_used JSONB, -- {"category": "IT", "city": "Wejherowo"}
|
|
|
|
-- Timing
|
|
searched_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
time_to_click_ms INTEGER, -- Czas od wyświetlenia do kliknięcia
|
|
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_search_queries_query ON search_queries(query_normalized);
|
|
CREATE INDEX IF NOT EXISTS idx_search_queries_searched_at ON search_queries(searched_at);
|
|
CREATE INDEX IF NOT EXISTS idx_search_queries_session ON search_queries(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_search_queries_has_results ON search_queries(has_results) WHERE has_results = FALSE;
|
|
|
|
COMMENT ON TABLE search_queries IS 'Historia wyszukiwań użytkowników w portalu';
|
|
|
|
-- ============================================================
|
|
-- 4. CONVERSION EVENTS - Nowa tabela
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS conversion_events (
|
|
id SERIAL PRIMARY KEY,
|
|
session_id INTEGER REFERENCES user_sessions(id) ON DELETE SET NULL,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
|
|
-- Typ konwersji
|
|
event_type VARCHAR(50) NOT NULL, -- register, login, contact_click, rsvp, message, classified
|
|
event_category VARCHAR(50), -- engagement, acquisition, activation
|
|
|
|
-- Kontekst
|
|
company_id INTEGER REFERENCES companies(id) ON DELETE SET NULL, -- Dla contact_click
|
|
target_type VARCHAR(50), -- email, phone, website, rsvp_event, etc.
|
|
target_value VARCHAR(500), -- np. adres email, id eventu
|
|
|
|
-- Źródło konwersji
|
|
source_page VARCHAR(500), -- URL strony na której nastąpiła konwersja
|
|
referrer VARCHAR(500),
|
|
|
|
-- Dodatkowe dane
|
|
event_metadata JSONB, -- Elastyczne dane kontekstowe
|
|
|
|
-- Timing
|
|
converted_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_conversion_events_type ON conversion_events(event_type);
|
|
CREATE INDEX IF NOT EXISTS idx_conversion_events_converted_at ON conversion_events(converted_at);
|
|
CREATE INDEX IF NOT EXISTS idx_conversion_events_session ON conversion_events(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_conversion_events_user ON conversion_events(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_conversion_events_company ON conversion_events(company_id) WHERE company_id IS NOT NULL;
|
|
|
|
COMMENT ON TABLE conversion_events IS 'Kluczowe konwersje: rejestracje, kontakty, RSVP';
|
|
|
|
-- ============================================================
|
|
-- 5. JS ERRORS - Nowa tabela
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS js_errors (
|
|
id SERIAL PRIMARY KEY,
|
|
session_id INTEGER REFERENCES user_sessions(id) ON DELETE SET NULL,
|
|
|
|
-- Błąd
|
|
message TEXT NOT NULL,
|
|
source VARCHAR(500), -- URL pliku JS
|
|
lineno INTEGER,
|
|
colno INTEGER,
|
|
stack TEXT, -- Stack trace
|
|
|
|
-- Kontekst
|
|
url VARCHAR(2000), -- URL strony gdzie wystąpił błąd
|
|
user_agent VARCHAR(500),
|
|
|
|
-- Agregacja
|
|
error_hash VARCHAR(64), -- SHA256 z message+source+lineno dla grupowania
|
|
|
|
occurred_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_js_errors_hash ON js_errors(error_hash);
|
|
CREATE INDEX IF NOT EXISTS idx_js_errors_occurred_at ON js_errors(occurred_at);
|
|
CREATE INDEX IF NOT EXISTS idx_js_errors_source ON js_errors(source);
|
|
|
|
COMMENT ON TABLE js_errors IS 'Błędy JavaScript zgłaszane z przeglądarek użytkowników';
|
|
|
|
-- ============================================================
|
|
-- 6. PERFORMANCE METRICS - Dodanie do page_views
|
|
-- ============================================================
|
|
|
|
ALTER TABLE page_views ADD COLUMN IF NOT EXISTS dom_content_loaded_ms INTEGER;
|
|
ALTER TABLE page_views ADD COLUMN IF NOT EXISTS load_time_ms INTEGER;
|
|
ALTER TABLE page_views ADD COLUMN IF NOT EXISTS first_paint_ms INTEGER;
|
|
ALTER TABLE page_views ADD COLUMN IF NOT EXISTS first_contentful_paint_ms INTEGER;
|
|
|
|
COMMENT ON COLUMN page_views.dom_content_loaded_ms IS 'Czas do DOMContentLoaded (ms)';
|
|
COMMENT ON COLUMN page_views.load_time_ms IS 'Pełny czas ładowania strony (ms)';
|
|
COMMENT ON COLUMN page_views.first_paint_ms IS 'Czas do First Paint (ms)';
|
|
COMMENT ON COLUMN page_views.first_contentful_paint_ms IS 'Czas do First Contentful Paint (ms)';
|
|
|
|
-- ============================================================
|
|
-- 7. AGREGATY DZIENNE - Rozszerzenie analytics_daily
|
|
-- ============================================================
|
|
|
|
-- Bounce rate (już istnieje, ale upewniamy się)
|
|
ALTER TABLE analytics_daily ADD COLUMN IF NOT EXISTS bounce_rate NUMERIC(5,2);
|
|
|
|
-- Nowe metryki
|
|
ALTER TABLE analytics_daily ADD COLUMN IF NOT EXISTS conversions_count INTEGER DEFAULT 0;
|
|
ALTER TABLE analytics_daily ADD COLUMN IF NOT EXISTS searches_count INTEGER DEFAULT 0;
|
|
ALTER TABLE analytics_daily ADD COLUMN IF NOT EXISTS searches_no_results INTEGER DEFAULT 0;
|
|
ALTER TABLE analytics_daily ADD COLUMN IF NOT EXISTS avg_scroll_depth NUMERIC(5,2);
|
|
ALTER TABLE analytics_daily ADD COLUMN IF NOT EXISTS js_errors_count INTEGER DEFAULT 0;
|
|
|
|
-- UTM breakdown (JSONB dla elastyczności)
|
|
ALTER TABLE analytics_daily ADD COLUMN IF NOT EXISTS utm_breakdown JSONB;
|
|
|
|
-- Konwersje wg typu
|
|
ALTER TABLE analytics_daily ADD COLUMN IF NOT EXISTS conversions_breakdown JSONB;
|
|
|
|
COMMENT ON COLUMN analytics_daily.bounce_rate IS 'Procent sesji z 1 pageview lub <10s';
|
|
COMMENT ON COLUMN analytics_daily.conversions_count IS 'Łączna liczba konwersji';
|
|
COMMENT ON COLUMN analytics_daily.searches_count IS 'Liczba wyszukiwań';
|
|
COMMENT ON COLUMN analytics_daily.searches_no_results IS 'Wyszukiwania bez wyników';
|
|
COMMENT ON COLUMN analytics_daily.avg_scroll_depth IS 'Średnia głębokość scrollowania (%)';
|
|
COMMENT ON COLUMN analytics_daily.utm_breakdown IS 'Rozkład sesji wg UTM source';
|
|
COMMENT ON COLUMN analytics_daily.conversions_breakdown IS 'Rozkład konwersji wg typu';
|
|
|
|
-- ============================================================
|
|
-- 8. POPULAR SEARCHES - Agregat dzienny
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS popular_searches_daily (
|
|
id SERIAL PRIMARY KEY,
|
|
date DATE NOT NULL,
|
|
query_normalized VARCHAR(500) NOT NULL,
|
|
|
|
search_count INTEGER DEFAULT 0,
|
|
unique_users INTEGER DEFAULT 0,
|
|
click_count INTEGER DEFAULT 0, -- Ile razy kliknięto wynik
|
|
avg_results_count NUMERIC(10,2),
|
|
|
|
UNIQUE(date, query_normalized)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_popular_searches_date ON popular_searches_daily(date);
|
|
|
|
COMMENT ON TABLE popular_searches_daily IS 'Popularne wyszukiwania - dzienne agregaty';
|
|
|
|
-- ============================================================
|
|
-- 9. HOURLY ACTIVITY - Dla wzorców czasowych
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS hourly_activity (
|
|
id SERIAL PRIMARY KEY,
|
|
date DATE NOT NULL,
|
|
hour INTEGER NOT NULL CHECK (hour >= 0 AND hour <= 23),
|
|
|
|
sessions_count INTEGER DEFAULT 0,
|
|
page_views_count INTEGER DEFAULT 0,
|
|
unique_users INTEGER DEFAULT 0,
|
|
|
|
UNIQUE(date, hour)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_hourly_activity_date ON hourly_activity(date);
|
|
|
|
COMMENT ON TABLE hourly_activity IS 'Aktywność wg godziny - dla analizy wzorców czasowych';
|
|
|
|
-- ============================================================
|
|
-- 10. UPRAWNIENIA
|
|
-- ============================================================
|
|
|
|
-- Nadanie uprawnień dla roli nordabiz_app
|
|
GRANT ALL ON TABLE search_queries TO nordabiz_app;
|
|
GRANT ALL ON TABLE conversion_events TO nordabiz_app;
|
|
GRANT ALL ON TABLE js_errors TO nordabiz_app;
|
|
GRANT ALL ON TABLE popular_searches_daily TO nordabiz_app;
|
|
GRANT ALL ON TABLE hourly_activity TO nordabiz_app;
|
|
|
|
GRANT USAGE, SELECT ON SEQUENCE search_queries_id_seq TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON SEQUENCE conversion_events_id_seq TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON SEQUENCE js_errors_id_seq TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON SEQUENCE popular_searches_daily_id_seq TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON SEQUENCE hourly_activity_id_seq TO nordabiz_app;
|
|
|
|
COMMIT;
|
|
|
|
-- ============================================================
|
|
-- WERYFIKACJA
|
|
-- ============================================================
|
|
|
|
-- Sprawdź czy wszystkie tabele istnieją
|
|
DO $$
|
|
BEGIN
|
|
RAISE NOTICE 'Weryfikacja migracji 033_analytics_expansion:';
|
|
RAISE NOTICE '- search_queries: %', (SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'search_queries');
|
|
RAISE NOTICE '- conversion_events: %', (SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'conversion_events');
|
|
RAISE NOTICE '- js_errors: %', (SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'js_errors');
|
|
RAISE NOTICE '- popular_searches_daily: %', (SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'popular_searches_daily');
|
|
RAISE NOTICE '- hourly_activity: %', (SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'hourly_activity');
|
|
RAISE NOTICE 'Migracja 033 zakończona pomyślnie!';
|
|
END $$;
|