0.2.45 - Server side git commits

This commit is contained in:
ponzischeme89
2026-08-10 20:39:26 +12:00
parent 56c1167382
commit 4c47a5f8a0
12 changed files with 321 additions and 132 deletions
+78 -2
View File
@@ -97,6 +97,28 @@ type PathStat struct {
Count int64 `json:"count"`
}
// JourneyStats is the server-derived health of foreground visits in a reporting window.
// An unfinished visit is only abandoned once it has been quiet for thirty minutes; until
// then it is active, so an open television does not immediately look like a failed flow.
type JourneyStats struct {
Events int64 `json:"events"`
Journeys int64 `json:"journeys"`
Viewers int64 `json:"viewers"`
Completed int64 `json:"completed"`
Abandoned int64 `json:"abandoned"`
Active int64 `json:"active"`
AverageSteps float64 `json:"averageSteps"`
AverageTimeMs int64 `json:"averageTimeMs"`
CompletionRate float64 `json:"completionRate"`
}
type JourneyActionStat struct {
Category string `json:"category"`
Action string `json:"action"`
Events int64 `json:"events"`
Journeys int64 `json:"journeys"`
}
// Event kinds. Impressions say a row was drawn; focus says the remote actually landed
// on it and for how long; select says something was opened from it.
const (
@@ -231,10 +253,64 @@ func (s *Store) AnalyticsUsers(ctx context.Context, since time.Time) ([]Analytic
return out, rows.Err()
}
func (s *Store) JourneyStats(ctx context.Context, userID string, since time.Time) (JourneyStats, error) {
var value JourneyStats
err := s.pool.QueryRow(ctx, `
WITH visits AS (
SELECT emby_user_id, journey_id, count(*) AS steps,
min(occurred_at) AS started_at, max(occurred_at) AS last_at,
bool_or(action = 'journey_end') AS completed
FROM journey_events
WHERE occurred_at >= $1 AND ($2 = '' OR emby_user_id = $2)
GROUP BY emby_user_id, journey_id
)
SELECT coalesce(sum(steps), 0), count(*), count(DISTINCT emby_user_id),
count(*) FILTER (WHERE completed),
count(*) FILTER (WHERE NOT completed AND last_at < now() - interval '30 minutes'),
count(*) FILTER (WHERE NOT completed AND last_at >= now() - interval '30 minutes'),
coalesce(avg(steps), 0)::double precision,
round(coalesce(avg(extract(epoch FROM (last_at - started_at)) * 1000)
FILTER (WHERE completed), 0))::bigint
FROM visits`, since, userID).Scan(
&value.Events, &value.Journeys, &value.Viewers, &value.Completed,
&value.Abandoned, &value.Active, &value.AverageSteps, &value.AverageTimeMs,
)
if err != nil {
return JourneyStats{}, fmt.Errorf("store: journey stats: %w", err)
}
finished := value.Completed + value.Abandoned
if finished > 0 {
value.CompletionRate = float64(value.Completed) / float64(finished)
}
return value, nil
}
func (s *Store) JourneyActionStats(ctx context.Context, userID string, since time.Time) ([]JourneyActionStat, error) {
rows, err := s.pool.Query(ctx, `
SELECT category, action, count(*), count(DISTINCT journey_id)
FROM journey_events
WHERE occurred_at >= $1 AND ($2 = '' OR emby_user_id = $2)
AND action NOT IN ('journey_start', 'journey_end')
GROUP BY category, action ORDER BY count(*) DESC, category, action`, since, userID)
if err != nil {
return nil, fmt.Errorf("store: journey action stats: %w", err)
}
defer rows.Close()
out := []JourneyActionStat{}
for rows.Next() {
var value JourneyActionStat
if err := rows.Scan(&value.Category, &value.Action, &value.Events, &value.Journeys); err != nil {
return nil, err
}
out = append(out, value)
}
return out, rows.Err()
}
func (s *Store) UserFeatureStats(ctx context.Context, userID string, since time.Time) ([]FeatureStat, error) {
rows, err := s.pool.Query(ctx, `
SELECT feature, count(*), max(occurred_at) FROM journey_events
WHERE emby_user_id=$1 AND occurred_at >= $2 AND feature <> ''
WHERE ($1 = '' OR emby_user_id=$1) AND occurred_at >= $2 AND feature <> ''
AND action NOT IN ('screen_view', 'journey_start', 'journey_end')
GROUP BY feature ORDER BY count(*) DESC, feature`, userID, since)
if err != nil {
@@ -259,7 +335,7 @@ func (s *Store) UserPaths(ctx context.Context, userID string, since time.Time) (
coalesce(nullif(target,''), nullif(screen,''), feature) AS node,
lag(coalesce(nullif(target,''), nullif(screen,''), feature)) OVER
(PARTITION BY journey_id ORDER BY sequence, occurred_at, id) AS previous
FROM journey_events WHERE emby_user_id=$1 AND occurred_at >= $2
FROM journey_events WHERE ($1 = '' OR emby_user_id=$1) AND occurred_at >= $2
), path_steps AS (
SELECT previous AS from_node, node AS to_node FROM ordered
WHERE previous IS NOT NULL AND node IS NOT NULL AND previous <> node