feat: remove Paths tab, move session length distribution to Overview
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Paths tab (entry/exit pages, transitions, drop-off) provided little
actionable value for association admin. Session length distribution
moved to Overview as compact card layout. Menu: 6 tabs -> 5.
Old /paths URL redirects to /overview.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-11 03:50:47 +01:00
parent 7a83719647
commit 289b8d6f31
2 changed files with 43 additions and 113 deletions

View File

@ -83,7 +83,9 @@ def user_insights():
elif tab == 'pages':
data = _tab_pages(db, start_date, days)
elif tab == 'paths':
data = _tab_paths(db, start_date, days)
# Redirect old paths tab to overview
from flask import redirect
return redirect(url_for('admin.user_insights', tab='overview', period=period))
elif tab == 'overview':
data = _tab_overview(db, start_date, days)
elif tab == 'chat':
@ -1497,6 +1499,30 @@ def _tab_overview(db, start_date, days):
})
companies_attention.sort(key=lambda x: x['issue_count'], reverse=True)
# Session length distribution (exclude bots)
session_length_sql = text("""
SELECT
CASE
WHEN pv_count = 1 THEN '1 strona'
WHEN pv_count = 2 THEN '2 strony'
WHEN pv_count BETWEEN 3 AND 5 THEN '3-5 stron'
WHEN pv_count BETWEEN 6 AND 10 THEN '6-10 stron'
ELSE '10+ stron'
END as bucket,
COUNT(*) as cnt
FROM (
SELECT pv.session_id, COUNT(*) as pv_count
FROM page_views pv
JOIN user_sessions us ON pv.session_id = us.id
WHERE pv.viewed_at >= :start_dt AND pv.session_id IS NOT NULL AND us.is_bot = false
GROUP BY pv.session_id
) session_counts
GROUP BY bucket
ORDER BY MIN(pv_count)
""")
session_lengths = db.execute(session_length_sql, {'start_dt': start_30d}).fetchall()
max_sl = max((r.cnt for r in session_lengths), default=1) or 1
return {
'filter_type': filter_type,
'kpi': kpi,
@ -1520,6 +1546,7 @@ def _tab_overview(db, start_date, days):
'max_company_views': max_company,
'companies_attention': companies_attention[:20],
'companies_attention_total': len(companies_attention),
'session_lengths': [{'bucket': r.bucket, 'count': r.cnt, 'bar_pct': int(r.cnt / max_sl * 100)} for r in session_lengths],
}

View File

@ -192,7 +192,6 @@
<a href="{{ url_for('admin.user_insights', tab='overview', period=period) }}" class="tab-link {% if tab == 'overview' %}active{% endif %}">Przegląd</a>
<a href="{{ url_for('admin.user_insights', tab='engagement', period=period) }}" class="tab-link {% if tab == 'engagement' %}active{% endif %}">Zaangażowanie</a>
<a href="{{ url_for('admin.user_insights', tab='pages', period=period) }}" class="tab-link {% if tab == 'pages' %}active{% endif %}">Strony</a>
<a href="{{ url_for('admin.user_insights', tab='paths', period=period) }}" class="tab-link {% if tab == 'paths' %}active{% endif %}">Ścieżki</a>
<a href="{{ url_for('admin.user_insights', tab='problems', period=period) }}" class="tab-link {% if tab == 'problems' %}active{% endif %}">Problemy</a>
<a href="{{ url_for('admin.user_insights', tab='chat', period=period) }}" class="tab-link {% if tab == 'chat' %}active{% endif %}">Chat & Konwersje</a>
</div>
@ -710,117 +709,6 @@
</div>
<!-- ============================================================ -->
<!-- TAB: PATHS -->
<!-- ============================================================ -->
{% elif tab == 'paths' %}
<div class="two-columns">
<!-- Entry pages -->
<div class="section-card">
<h2>Strony wejściowe (top 10)</h2>
{% for p in data.entry_pages %}
<div class="bar-chart-row">
<div class="bar-chart-label" style="width: 200px; min-width: 200px; font-size: var(--font-size-xs); word-break: break-all;" title="{{ p.path }}">{{ p.label }}</div>
<div class="bar-chart-bar">
<div class="bar-chart-fill" style="width: {{ p.bar_pct }}%; background: #22c55e;">
<span>{{ p.count }}</span>
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Exit pages -->
<div class="section-card">
<h2>Strony wyjściowe (top 10)</h2>
{% for p in data.exit_pages %}
<div class="bar-chart-row">
<div class="bar-chart-label" style="width: 200px; min-width: 200px; font-size: var(--font-size-xs); word-break: break-all;" title="{{ p.path }}">{{ p.label }}</div>
<div class="bar-chart-bar">
<div class="bar-chart-fill" style="width: {{ p.bar_pct }}%; background: #ef4444;">
<span>{{ p.count }}</span>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Top transitions -->
<div class="section-card">
<h2>Popularne przejścia (top 30)</h2>
<div class="table-scroll" style="max-height: 400px;">
<table class="data-table">
<thead>
<tr>
<th>Ze strony</th>
<th style="width: 40px;"></th>
<th>Na stronę</th>
<th style="width: 80px;">Liczba</th>
</tr>
</thead>
<tbody>
{% for t in data.transitions %}
<tr>
<td style="font-size: var(--font-size-xs);" title="{{ t.from }}">{{ t.from_label }}</td>
<td class="transition-arrow" style="text-align: center;"></td>
<td style="font-size: var(--font-size-xs);" title="{{ t.to }}">{{ t.to_label }}</td>
<td style="font-weight: 600; text-align: right;">{{ t.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="two-columns">
<!-- Drop-off pages -->
<div class="section-card">
<h2>Strony z odpływem</h2>
<div class="table-scroll">
<table class="data-table">
<thead>
<tr>
<th>Strona</th>
<th>Odsłony</th>
<th>Wyjścia</th>
<th>Wsp. wyjścia</th>
</tr>
</thead>
<tbody>
{% for d in data.dropoff %}
<tr>
<td style="font-size: var(--font-size-xs);" title="{{ d.path }}">{{ d.label }}</td>
<td>{{ d.views }}</td>
<td>{{ d.exits }}</td>
<td>
<span {% if d.exit_rate > 70 %}style="color: var(--error); font-weight: 600;"{% elif d.exit_rate > 50 %}style="color: #d97706;"{% endif %}>
{{ d.exit_rate }}%
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Session length distribution -->
<div class="section-card">
<h2>Rozkład długości sesji</h2>
{% for s in data.session_lengths %}
<div class="bar-chart-row">
<div class="bar-chart-label">{{ s.bucket }}</div>
<div class="bar-chart-bar">
<div class="bar-chart-fill" style="width: {{ s.bar_pct }}%;">
<span>{{ s.count }}</span>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- ============================================================ -->
<!-- TAB: OVERVIEW -->
<!-- ============================================================ -->
@ -985,6 +873,21 @@
</div>
</div>
<!-- Session length distribution -->
{% if data.session_lengths %}
<div class="section-card">
<h2>Ile stron ogląda się w jednej wizycie</h2>
<div style="display: flex; gap: var(--spacing-md); flex-wrap: wrap;">
{% for s in data.session_lengths %}
<div style="flex: 1; min-width: 100px; text-align: center; padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div style="font-size: var(--font-size-2xl); font-weight: 700; color: var(--primary);">{{ s.count }}</div>
<div style="font-size: var(--font-size-sm); color: var(--text-secondary);">{{ s.bucket }}</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Referrer Sources + Company Popularity -->
<div class="two-columns">
<!-- Referrer Sources -->