mirror of
https://github.com/Kugelschieber/marvinblum.git
synced 2026-01-18 14:50:27 +00:00
Total visitor and page visits statistics page.
This commit is contained in:
102
tracking/statistics.go
Normal file
102
tracking/statistics.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package tracking
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/emvi/pirsch"
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
statisticsDateFormat = "2006-01-02"
|
||||
)
|
||||
|
||||
type PageVisits struct {
|
||||
Path string
|
||||
Labels template.JS
|
||||
Data template.JS
|
||||
}
|
||||
|
||||
func GetTotalVisitors(start int) (template.JS, template.JS) {
|
||||
query := `SELECT "date" "day",
|
||||
CASE WHEN "visitors_per_day".visitors IS NULL THEN 0 ELSE "visitors_per_day".visitors END
|
||||
FROM (SELECT * FROM generate_series(date($1), date(now()), interval '1 day') "date") AS date_series
|
||||
LEFT JOIN "visitors_per_day" ON date("visitors_per_day"."day") = date("date")
|
||||
ORDER BY "date" ASC`
|
||||
startTime := today()
|
||||
startTime = startTime.Add(-time.Hour * 24 * time.Duration(start-1))
|
||||
logbuch.Debug("Reading total visitors since", logbuch.Fields{"since": startTime})
|
||||
var visitors []pirsch.VisitorsPerDay
|
||||
|
||||
if err := db.Select(&visitors, query, startTime); err != nil {
|
||||
logbuch.Error("Error reading total visitors", logbuch.Fields{"err": err, "since": startTime})
|
||||
return "", ""
|
||||
}
|
||||
|
||||
today := today()
|
||||
visitorsToday, err := store.VisitorsPerDay(today)
|
||||
|
||||
if err != nil {
|
||||
logbuch.Error("Error reading total visitors for today", logbuch.Fields{"err": err, "since": startTime})
|
||||
return "", ""
|
||||
}
|
||||
|
||||
visitors[len(visitors)-1].Visitors = visitorsToday
|
||||
return getLabelsAndData(visitors)
|
||||
}
|
||||
|
||||
func GetPageVisits(start int) []PageVisits {
|
||||
pathsQuery := `SELECT * FROM (SELECT DISTINCT "path" FROM "visitors_per_page" WHERE day >= $1) AS paths ORDER BY length("path") ASC`
|
||||
query := `SELECT "date" "day",
|
||||
CASE WHEN "visitors_per_page".visitors IS NULL THEN 0 ELSE "visitors_per_page".visitors END
|
||||
FROM (SELECT * FROM generate_series(date($1), date(now() - INTERVAL '1 day'), interval '1 day') "date") AS date_series
|
||||
LEFT JOIN "visitors_per_page" ON date("visitors_per_page"."day") = date("date")
|
||||
WHERE "visitors_per_page" IS NULL OR "visitors_per_page"."path" = $2
|
||||
ORDER BY "date" ASC, length("visitors_per_page"."path") ASC`
|
||||
startTime := today()
|
||||
startTime = startTime.Add(-time.Hour * 24 * time.Duration(start-1))
|
||||
logbuch.Debug("Reading page visits", logbuch.Fields{"since": startTime})
|
||||
var paths []string
|
||||
|
||||
if err := db.Select(&paths, pathsQuery, startTime); err != nil {
|
||||
logbuch.Error("Error reading distinct paths", logbuch.Fields{"err": err})
|
||||
return nil
|
||||
}
|
||||
|
||||
pageVisits := make([]PageVisits, len(paths))
|
||||
|
||||
for i, path := range paths {
|
||||
var visitors []pirsch.VisitorsPerDay
|
||||
|
||||
if err := db.Select(&visitors, query, startTime, path); err != nil {
|
||||
logbuch.Error("Error reading visitor for path", logbuch.Fields{"err": err, "since": startTime})
|
||||
return nil
|
||||
}
|
||||
|
||||
labels, data := getLabelsAndData(visitors)
|
||||
pageVisits[i] = PageVisits{path, labels, data}
|
||||
}
|
||||
|
||||
return pageVisits
|
||||
}
|
||||
|
||||
func getLabelsAndData(visitors []pirsch.VisitorsPerDay) (template.JS, template.JS) {
|
||||
var labels strings.Builder
|
||||
var dp strings.Builder
|
||||
|
||||
for _, point := range visitors {
|
||||
labels.WriteString(fmt.Sprintf("'%s',", point.Day.Format(statisticsDateFormat)))
|
||||
dp.WriteString(fmt.Sprintf("%d,", point.Visitors))
|
||||
}
|
||||
|
||||
labelsStr := labels.String()
|
||||
dataStr := dp.String()
|
||||
return template.JS(labelsStr[:len(labelsStr)-1]), template.JS(dataStr[:len(dataStr)-1])
|
||||
}
|
||||
|
||||
func today() time.Time {
|
||||
now := time.Now()
|
||||
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/emvi/pirsch"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -14,6 +15,9 @@ const (
|
||||
connectionString = `host=%s port=%s user=%s password=%s dbname=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s connectTimeout=%s timezone=%s`
|
||||
)
|
||||
|
||||
var db *sqlx.DB
|
||||
var store pirsch.Store
|
||||
|
||||
func NewTracker() *pirsch.Tracker {
|
||||
logbuch.Info("Connecting to database...")
|
||||
host := os.Getenv("MB_DB_HOST")
|
||||
@@ -29,26 +33,26 @@ func NewTracker() *pirsch.Tracker {
|
||||
timezone := zone + strconv.Itoa(-offset/3600)
|
||||
logbuch.Info("Setting time zone", logbuch.Fields{"timezone": timezone})
|
||||
connectionStr := fmt.Sprintf(connectionString, host, port, user, password, schema, sslMode, sslCert, sslKey, sslRootCert, "30", timezone)
|
||||
db, err := sql.Open("postgres", connectionStr)
|
||||
conn, err := sql.Open("postgres", connectionStr)
|
||||
|
||||
if err != nil {
|
||||
logbuch.Fatal("Error connecting to database", logbuch.Fields{"err": err})
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := db.Ping(); err != nil {
|
||||
if err := conn.Ping(); err != nil {
|
||||
logbuch.Fatal("Error pinging database", logbuch.Fields{"err": err})
|
||||
return nil
|
||||
}
|
||||
|
||||
store := pirsch.NewPostgresStore(db)
|
||||
db = sqlx.NewDb(conn, "postgres")
|
||||
store = pirsch.NewPostgresStore(conn)
|
||||
tracker := pirsch.NewTracker(store, nil)
|
||||
processor := pirsch.NewProcessor(store)
|
||||
processTrackingData(processor)
|
||||
pirsch.RunAtMidnight(func() {
|
||||
processTrackingData(processor)
|
||||
})
|
||||
|
||||
return tracker
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user