Files
TimeTracker/app/services/usage_stats_service.py
T
Dries Peeters b0dde80ba9 feat(web): high-visibility support modal, prompts, and supporter UX
Add a support modal with usage stats, tier and license links, share control, and offline-safe outbound CTAs. Surface support from the header, sidebar, user menu, dashboard card, and settings "Support & Community" section without hiding entry points when a supporter license is active.

Introduce UsageStatsService and a persisted users.support_stats_reports_generated counter incremented on key report exports and custom report views. Add SupportPromptService for session-scoped soft toasts (after export, dashboard milestones, long session via POST /donate/request-soft-prompt).

Wire consent-aware track_event names support.* and mirror funnel rows in DonationInteraction; fix has_recent_donation_click to treat link_clicked as a recent click. Document events and SUPPORT_* / migration notes in docs.

Tests: tests/test_support_services.py for prompt and usage stats behavior.
2026-04-15 10:55:37 +02:00

57 lines
1.9 KiB
Python

"""Aggregated usage stats for support modal, dashboard widget, and prompts."""
from __future__ import annotations
from typing import Any, Dict, Optional
from app import db
class UsageStatsService:
"""Read/write lightweight counters and engagement metrics for support UI."""
@staticmethod
def get_for_user(user_id: int, month_hours: Optional[float] = None) -> Dict[str, Any]:
from app.models import DonationInteraction, User
base = DonationInteraction.get_user_engagement_metrics(user_id) or {}
reports_count = 0
try:
u = db.session.get(User, user_id)
if u is not None:
reports_count = int(getattr(u, "support_stats_reports_generated", 0) or 0)
except Exception:
reports_count = 0
out = {
"total_hours": float(base.get("total_hours") or 0.0),
"time_entries_count": int(base.get("time_entries_count") or 0),
"days_since_signup": int(base.get("days_since_signup") or 0),
"reports_generated_count": reports_count,
}
if month_hours is not None:
out["month_hours"] = float(month_hours)
return out
@staticmethod
def increment_reports_generated(user_id: int) -> None:
"""Persist +1 report generation (export or custom report view). Never raises."""
if not user_id:
return
try:
from sqlalchemy import text
db.session.execute(
text(
"UPDATE users SET support_stats_reports_generated = "
"COALESCE(support_stats_reports_generated, 0) + 1 WHERE id = :uid"
),
{"uid": user_id},
)
db.session.commit()
except Exception:
try:
db.session.rollback()
except Exception:
pass