feat(query-api): add richer stats and retention
This commit is contained in:
@@ -12,6 +12,7 @@ type SessionRow struct {
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
EndedAt *time.Time `json:"ended_at,omitempty"`
|
||||
Framework string `json:"framework"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Host string `json:"host"`
|
||||
RunCount int `json:"run_count"`
|
||||
}
|
||||
@@ -33,8 +34,10 @@ func (d *DB) ListSessions(ctx context.Context, f SessionsFilter) ([]SessionRow,
|
||||
f.Limit = 200
|
||||
}
|
||||
|
||||
// Build query dynamically
|
||||
var conditions []string
|
||||
// Build query dynamically using a CTE so cursor compares against
|
||||
// the grouped started_at rather than individual event timestamps.
|
||||
var innerConditions []string
|
||||
var outerConditions []string
|
||||
var args []any
|
||||
argN := 1
|
||||
|
||||
@@ -43,50 +46,63 @@ func (d *DB) ListSessions(ctx context.Context, f SessionsFilter) ([]SessionRow,
|
||||
t := time.Now().Add(-24 * time.Hour)
|
||||
f.From = &t
|
||||
}
|
||||
conditions = append(conditions, fmt.Sprintf("ts >= $%d", argN))
|
||||
innerConditions = append(innerConditions, fmt.Sprintf("ts >= $%d", argN))
|
||||
args = append(args, *f.From)
|
||||
argN++
|
||||
|
||||
if f.To != nil {
|
||||
conditions = append(conditions, fmt.Sprintf("ts <= $%d", argN))
|
||||
innerConditions = append(innerConditions, fmt.Sprintf("ts <= $%d", argN))
|
||||
args = append(args, *f.To)
|
||||
argN++
|
||||
}
|
||||
|
||||
if f.Framework != "" {
|
||||
conditions = append(conditions, fmt.Sprintf("source_framework = $%d", argN))
|
||||
innerConditions = append(innerConditions, fmt.Sprintf("source_framework = $%d", argN))
|
||||
args = append(args, f.Framework)
|
||||
argN++
|
||||
}
|
||||
|
||||
// Host filter applies to an aggregate, so it goes in the outer WHERE
|
||||
if f.Host != "" {
|
||||
conditions = append(conditions, fmt.Sprintf("payload->'event'->'source'->>'host' = $%d", argN))
|
||||
outerConditions = append(outerConditions, fmt.Sprintf("host = $%d", argN))
|
||||
args = append(args, f.Host)
|
||||
argN++
|
||||
}
|
||||
|
||||
// Cursor compares against grouped started_at, so it goes in the outer WHERE
|
||||
if f.Cursor != nil {
|
||||
conditions = append(conditions, fmt.Sprintf("ts < $%d", argN))
|
||||
outerConditions = append(outerConditions, fmt.Sprintf("started_at < $%d", argN))
|
||||
args = append(args, *f.Cursor)
|
||||
argN++
|
||||
}
|
||||
|
||||
where := strings.Join(conditions, " AND ")
|
||||
innerWhere := strings.Join(innerConditions, " AND ")
|
||||
|
||||
outerWhere := ""
|
||||
if len(outerConditions) > 0 {
|
||||
outerWhere = "WHERE " + strings.Join(outerConditions, " AND ")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
SELECT
|
||||
session_id,
|
||||
MIN(ts) as started_at,
|
||||
MAX(ts) as ended_at,
|
||||
MAX(source_framework) as framework,
|
||||
MAX(payload->'event'->'source'->>'host') as host,
|
||||
COUNT(DISTINCT run_id) as run_count
|
||||
FROM events
|
||||
WHERE session_id IS NOT NULL AND %s
|
||||
GROUP BY session_id
|
||||
WITH session_groups AS (
|
||||
SELECT
|
||||
session_id,
|
||||
MIN(ts) as started_at,
|
||||
MAX(CASE WHEN type = 'session.end' THEN ts END) as ended_at,
|
||||
MAX(source_framework) as framework,
|
||||
MAX(client_id) as client_id,
|
||||
MAX(payload->'event'->'source'->>'host') as host,
|
||||
COUNT(DISTINCT run_id) as run_count
|
||||
FROM events
|
||||
WHERE session_id IS NOT NULL AND %s
|
||||
GROUP BY session_id
|
||||
)
|
||||
SELECT session_id, started_at, ended_at, framework, client_id, host, run_count
|
||||
FROM session_groups
|
||||
%s
|
||||
ORDER BY started_at DESC
|
||||
LIMIT $%d
|
||||
`, where, argN)
|
||||
`, innerWhere, outerWhere, argN)
|
||||
args = append(args, f.Limit+1) // fetch one extra to detect next page
|
||||
|
||||
rows, err := d.sql.QueryContext(ctx, query, args...)
|
||||
@@ -98,10 +114,14 @@ func (d *DB) ListSessions(ctx context.Context, f SessionsFilter) ([]SessionRow,
|
||||
var out []SessionRow
|
||||
for rows.Next() {
|
||||
var r SessionRow
|
||||
var clientID *string
|
||||
var host *string
|
||||
if err := rows.Scan(&r.SessionID, &r.StartedAt, &r.EndedAt, &r.Framework, &host, &r.RunCount); err != nil {
|
||||
if err := rows.Scan(&r.SessionID, &r.StartedAt, &r.EndedAt, &r.Framework, &clientID, &host, &r.RunCount); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if clientID != nil {
|
||||
r.ClientID = *clientID
|
||||
}
|
||||
if host != nil {
|
||||
r.Host = *host
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user