From d81f356466b116b760ee5b7b77b441f5fa5ee5b8 Mon Sep 17 00:00:00 2001 From: Jo Vandeginste Date: Thu, 11 Apr 2024 19:13:35 +0200 Subject: [PATCH] Convert user statistics to use the new function The data structure in the statistics.go file was updated to no longer add user statistics. The UserStatistics template in the user_statistics.html file was also updated to reflect the changes in the data structure. The changes ensure that user statistics are retrieved and displayed correctly in the user statistics page. Signed-off-by: Jo Vandeginste --- pkg/app/data.go | 15 --- pkg/app/handlers.go | 8 -- pkg/app/users_handlers.go | 4 - pkg/database/statistics.go | 34 ++---- pkg/database/user_statistics.go | 207 ++++++-------------------------- views/user/user_statistics.html | 74 +++++------- 6 files changed, 78 insertions(+), 264 deletions(-) diff --git a/pkg/app/data.go b/pkg/app/data.go index ea4149cc..696ecc79 100644 --- a/pkg/app/data.go +++ b/pkg/app/data.go @@ -96,21 +96,6 @@ func (a *App) addUsers(data map[string]interface{}) error { return nil } -func (a *App) addUserStatistics(u *database.User, data map[string]interface{}) error { - if u == nil { - return nil - } - - us, err := u.Statistics(a.db) - if err != nil { - return err - } - - data["UserStatistics"] = us - - return nil -} - func (a *App) getWorkout(c echo.Context) (*database.Workout, error) { id, err := strconv.Atoi(c.Param("id")) if err != nil { diff --git a/pkg/app/handlers.go b/pkg/app/handlers.go index 0b7b9c45..ce0d28bc 100644 --- a/pkg/app/handlers.go +++ b/pkg/app/handlers.go @@ -25,10 +25,6 @@ func (a *App) statisticsHandler(c echo.Context) error { return a.redirectWithError(c, a.echo.Reverse("user-signout"), err) } - if err := a.addUserStatistics(u, data); err != nil { - return a.redirectWithError(c, a.echo.Reverse("user-signout"), err) - } - data["user"] = u return c.Render(http.StatusOK, "user_statistics.html", data) @@ -46,10 +42,6 @@ func (a *App) dashboardHandler(c echo.Context) error { return a.redirectWithError(c, a.echo.Reverse("user-signout"), err) } - if err := a.addUserStatistics(u, data); err != nil { - return a.redirectWithError(c, a.echo.Reverse("user-signout"), err) - } - if err := a.addUsers(data); err != nil { return a.redirectWithError(c, a.echo.Reverse("user-signout"), err) } diff --git a/pkg/app/users_handlers.go b/pkg/app/users_handlers.go index 8cd5bf58..3bf5af03 100644 --- a/pkg/app/users_handlers.go +++ b/pkg/app/users_handlers.go @@ -103,9 +103,5 @@ func (a *App) userShowHandler(c echo.Context) error { return a.redirectWithError(c, a.echo.Reverse("user-signout"), err) } - if err := a.addUserStatistics(u, data); err != nil { - return a.redirectWithError(c, a.echo.Reverse("user-signout"), err) - } - return c.Render(http.StatusOK, "user_show.html", data) } diff --git a/pkg/database/statistics.go b/pkg/database/statistics.go index 79b411df..5329cc51 100644 --- a/pkg/database/statistics.go +++ b/pkg/database/statistics.go @@ -1,9 +1,5 @@ package database -import ( - "time" -) - type StatConfig struct { Since string `query:"since"` Per string `query:"per"` @@ -26,27 +22,17 @@ func (sc *StatConfig) GetSince() string { return sc.Since } -type Statistics struct { - UserID uint - Buckets map[string]map[WorkoutType]Bucket -} - -type Bucket struct { - Bucket string `json:",omitempty"` - WorkoutType WorkoutType - Workouts int - Distance float64 `json:",omitempty"` - Up float64 `json:",omitempty"` - Duration time.Duration `json:",omitempty"` - AverageSpeed float64 `json:",omitempty"` - AverageSpeedNoPause float64 `json:",omitempty"` - MaxSpeed float64 `json:",omitempty"` +func (u *User) GetDefaultStatistics() (*Statistics, error) { + return u.GetStatistics(StatConfig{ + Since: "-1 year", + Per: "month", + }) } func (u *User) GetStatistics(statConfig StatConfig) (*Statistics, error) { r := &Statistics{ UserID: u.ID, - Buckets: map[string]map[WorkoutType]Bucket{}, + Buckets: map[WorkoutType]map[string]Bucket{}, } rows, err := u.db. @@ -79,11 +65,11 @@ func (u *User) GetStatistics(statConfig StatConfig) (*Statistics, error) { return nil, err } - if r.Buckets[result.Bucket] == nil { - r.Buckets[result.Bucket] = map[WorkoutType]Bucket{} + if r.Buckets[result.WorkoutType] == nil { + r.Buckets[result.WorkoutType] = map[string]Bucket{} } - r.Buckets[result.Bucket][result.WorkoutType] = result + r.Buckets[result.WorkoutType][result.Bucket] = result } return r, nil @@ -143,7 +129,7 @@ func (u *User) GetRecords(t WorkoutType) (*WorkoutRecord, error) { r := &WorkoutRecord{WorkoutType: t} - mapping := map[*record]string{ + mapping := map[*float64Record]string{ &r.Distance: "max(total_distance)", &r.MaxSpeed: "max(max_speed)", &r.TotalUp: "max(total_up)", diff --git a/pkg/database/user_statistics.go b/pkg/database/user_statistics.go index 3f6347b9..cf9b58eb 100644 --- a/pkg/database/user_statistics.go +++ b/pkg/database/user_statistics.go @@ -2,197 +2,62 @@ package database import ( "time" - - "gorm.io/gorm" ) type ( - record struct { - Value float64 - Date time.Time - ID uint + Statistics struct { + UserID uint + Buckets map[WorkoutType]map[string]Bucket } - WorkoutStatistics map[string]*WorkoutStatistic + Bucket struct { + Bucket string `json:",omitempty"` + WorkoutType WorkoutType + Workouts int + Distance float64 `json:",omitempty"` + Up float64 `json:",omitempty"` + Duration time.Duration `json:",omitempty"` + AverageSpeed float64 `json:",omitempty"` + AverageSpeedNoPause float64 `json:",omitempty"` + MaxSpeed float64 `json:",omitempty"` + } - WorkoutStatistic struct { - WorkoutType WorkoutType - Total Totals - PerYear map[int]*Totals - PerMonth map[int]map[int]*Totals - Records WorkoutRecord + float64Record struct { + Value float64 + Date time.Time + ID uint } - Totals struct { - WorkoutType WorkoutType - Workouts int - Distance float64 - Up float64 - Duration time.Duration - AverageSpeed float64 - AverageSpeedNoPause float64 - MaxSpeed float64 + durationRecord struct { + Value time.Duration + Date time.Time + ID uint } WorkoutRecord struct { WorkoutType WorkoutType Active bool - AverageSpeed record - AverageSpeedNoPause record - MaxSpeed record - Distance record - TotalUp record - Duration struct { - Value time.Duration - Date time.Time - ID uint - } + AverageSpeed float64Record + AverageSpeedNoPause float64Record + MaxSpeed float64Record + Distance float64Record + TotalUp float64Record + Duration durationRecord } ) -func (t *Totals) AverageSpeedKPH() float64 { - return 3.6 * t.AverageSpeed -} - -func (t *Totals) AverageSpeedNoPauseKPH() float64 { - return 3.6 * t.AverageSpeedNoPause -} - -func (t *Totals) MaxSpeedKPH() float64 { - return 3.6 * t.MaxSpeed -} - -func NewWorkoutStatistic(t WorkoutType) *WorkoutStatistic { - return &WorkoutStatistic{ - WorkoutType: t, - Records: WorkoutRecord{WorkoutType: t}, - Total: Totals{WorkoutType: t}, - PerYear: map[int]*Totals{}, - PerMonth: map[int]map[int]*Totals{}, - } -} - -func (r *record) CheckAndSwap(value float64, id uint, date *time.Time) { - if r.Value < value { - r.Value = value - r.Date = *date - r.ID = id - } -} - -func (us *WorkoutStatistic) Add(w *Workout) { - us.Total.Workouts++ - us.Total.Distance += w.Data.TotalDistance - us.Total.Duration += w.Data.TotalDuration - us.Total.Up += w.Data.TotalUp - us.Total.AverageSpeed += w.Data.AverageSpeed() - us.Total.AverageSpeedNoPause += w.Data.AverageSpeedNoPause() - us.Total.MaxSpeed += w.Data.MaxSpeed - - d := w.Date - year := d.Year() - month := int(d.Month()) - - us.AddYear(us.WorkoutType, year, w) - us.AddMonth(us.WorkoutType, year, month, w) -} - -func NewTotal(t WorkoutType, d *MapData) *Totals { - return &Totals{ - Workouts: 1, - WorkoutType: t, - Distance: d.TotalDistance, - Up: d.TotalUp, - Duration: d.TotalDuration, - AverageSpeed: d.AverageSpeed(), - AverageSpeedNoPause: d.AverageSpeedNoPause(), - MaxSpeed: d.MaxSpeed, - } +func (b Bucket) DistanceKM() float64 { + return b.Distance / 1000 } -func calcNewAvg(oldValue, newValue float64, newCounter int) float64 { - c := float64(newCounter) - return ((oldValue * (c - 1)) + newValue) / c +func (b Bucket) AverageSpeedNoPauseKPH() float64 { + return 3.6 * b.AverageSpeedNoPause } -func (t *Totals) Add(d *MapData) { - t.Workouts++ - t.Distance += d.TotalDistance - t.Duration += d.TotalDuration - t.Up += d.TotalUp - - t.AverageSpeed = calcNewAvg(t.AverageSpeed, d.AverageSpeed(), t.Workouts) - t.AverageSpeedNoPause = calcNewAvg(t.AverageSpeedNoPause, d.AverageSpeedNoPause(), t.Workouts) - - if d.MaxSpeed > t.MaxSpeed { - t.MaxSpeed = d.MaxSpeed - } +func (b Bucket) AverageSpeedKPH() float64 { + return 3.6 * b.AverageSpeed } -func (us *WorkoutStatistic) AddMonth(t WorkoutType, year, month int, w *Workout) { - if _, ok := us.PerMonth[year]; !ok { - us.PerMonth[year] = map[int]*Totals{} - } - - entry, ok := us.PerMonth[year][month] - if !ok { - us.PerMonth[year][month] = NewTotal(t, w.Data) - - return - } - - entry.Add(w.Data) -} - -func (us *WorkoutStatistic) AddYear(t WorkoutType, year int, w *Workout) { - entry, ok := us.PerYear[year] - if !ok { - us.PerYear[year] = NewTotal(t, w.Data) - - return - } - - entry.Add(w.Data) -} - -func (u *User) Statistics(db *gorm.DB) (WorkoutStatistics, error) { - us := WorkoutStatistics{} - - workouts, err := u.GetWorkouts(db) - if err != nil { - return nil, err - } - - for _, w := range workouts { - if !w.Type.IsDistance() { - continue - } - - s, ok := us[w.Type.String()] - if !ok { - s = NewWorkoutStatistic(w.Type) - us[w.Type.String()] = s - } - - s.Records.Active = true - s.Add(w) - - s.Records.CheckAndSwap(w) - } - - return us, nil -} - -func (wr *WorkoutRecord) CheckAndSwap(w *Workout) { - wr.AverageSpeedNoPause.CheckAndSwap(w.Data.AverageSpeedNoPause(), w.ID, w.Date) - wr.AverageSpeed.CheckAndSwap(w.Data.AverageSpeed(), w.ID, w.Date) - wr.MaxSpeed.CheckAndSwap(w.Data.MaxSpeed, w.ID, w.Date) - wr.Distance.CheckAndSwap(w.Data.TotalDistance, w.ID, w.Date) - wr.TotalUp.CheckAndSwap(w.Data.TotalUp, w.ID, w.Date) - - if w.Data.TotalDuration > wr.Duration.Value { - wr.Duration.Value = w.Data.TotalDuration - wr.Duration.ID = w.ID - wr.Duration.Date = *w.Date - } +func (b Bucket) MaxSpeedKPH() float64 { + return 3.6 * b.MaxSpeed } diff --git a/views/user/user_statistics.html b/views/user/user_statistics.html index 9d37a39c..8ca20d37 100644 --- a/views/user/user_statistics.html +++ b/views/user/user_statistics.html @@ -11,6 +11,8 @@

{{ i18n "Your progress the past year" }}

+ {{ $stats := CurrentUser.GetDefaultStatistics }} +
@@ -173,16 +175,14 @@

{{ i18n "Max speed" }}

type: "bar", data: { datasets: [ - {{ range $type, $stats := .UserStatistics }} - {{ if $stats.WorkoutType.IsDistance }} + {{ range $type, $PerMonth := $stats.Buckets }} + {{ if $type.IsDistance }} { - label: "{{ i18n $stats.WorkoutType.String }}", + label: "{{ i18n $type.String }}", data: [ - {{ range $y, $yv := $stats.PerMonth -}} - {{- range $m, $mv := $yv -}} - { x: "{{ $y }}-{{ printf "%02d" $m }}", y: {{ $mv.Workouts }} }, + {{- range $PerMonth -}} + { x: "{{ .Bucket }}", y: {{ .Workouts }} }, {{ end -}} - {{- end }} ], }, {{ end }} @@ -198,16 +198,14 @@

{{ i18n "Max speed" }}

type: "bar", data: { datasets: [ - {{ range $type, $stats := .UserStatistics }} - {{ if $stats.WorkoutType.IsDistance }} + {{ range $type, $PerMonth := $stats.Buckets }} + {{ if $type.IsDistance }} { - label: "{{ i18n $stats.WorkoutType.String }}", + label: "{{ i18n $type.String }}", data: [ - {{ range $y, $yv := $stats.PerMonth -}} - {{- range $m, $mv := $yv -}} - { x: "{{ $y }}-{{ printf "%02d" $m }}", y: {{ $mv.Duration | NumericDuration }} }, + {{- range $PerMonth -}} + { x: "{{ .Bucket }}", y: {{ .Duration | NumericDuration }} }, {{ end -}} - {{- end }} ], }, {{ end }} @@ -221,16 +219,14 @@

{{ i18n "Max speed" }}

type: "bar", data: { datasets: [ - {{ range $type, $stats := .UserStatistics }} - {{ if $stats.WorkoutType.IsDistance }} + {{ range $type, $PerMonth := $stats.Buckets }} + {{ if $type.IsDistance }} { - label: "{{ i18n $stats.WorkoutType.String }}", + label: "{{ i18n $type.String }}", data: [ - {{ range $y, $yv := $stats.PerMonth -}} - {{- range $m, $mv := $yv -}} - { x: "{{ $y }}-{{ printf "%02d" $m }}", y: {{ $mv.Distance }} / 1000 }, + {{- range $PerMonth -}} + { x: "{{ .Bucket }}", y: {{ .DistanceKM }} }, {{ end -}} - {{- end }} ], }, {{ end }} @@ -244,16 +240,14 @@

{{ i18n "Max speed" }}

type: "bar", data: { datasets: [ - {{ range $type, $stats := .UserStatistics }} - {{ if $stats.WorkoutType.IsDistance }} + {{ range $type, $PerMonth := $stats.Buckets }} + {{ if $type.IsDistance }} { - label: "{{ i18n $stats.WorkoutType.String }}", + label: "{{ i18n $type.String }}", data: [ - {{ range $y, $yv := $stats.PerMonth -}} - {{- range $m, $mv := $yv -}} - { x: "{{ $y }}-{{ printf "%02d" $m }}", y: {{ $mv.AverageSpeedKPH }} }, + {{- range $PerMonth -}} + { x: "{{ .Bucket }}", y: {{ .AverageSpeedKPH }} }, {{ end -}} - {{- end }} ], }, {{ end }} @@ -267,16 +261,14 @@

{{ i18n "Max speed" }}

type: "bar", data: { datasets: [ - {{ range $type, $stats := .UserStatistics }} - {{ if $stats.WorkoutType.IsDistance }} + {{ range $type, $PerMonth := $stats.Buckets }} + {{ if $type.IsDistance }} { - label: "{{ i18n $stats.WorkoutType.String }}", + label: "{{ i18n $type.String }}", data: [ - {{ range $y, $yv := $stats.PerMonth -}} - {{- range $m, $mv := $yv -}} - { x: "{{ $y }}-{{ printf "%02d" $m }}", y: {{ $mv.AverageSpeedNoPauseKPH }} }, + {{- range $PerMonth -}} + { x: "{{ .Bucket }}", y: {{ .AverageSpeedNoPauseKPH }} }, {{ end -}} - {{- end }} ], }, {{ end }} @@ -290,16 +282,14 @@

{{ i18n "Max speed" }}

type: "bar", data: { datasets: [ - {{ range $type, $stats := .UserStatistics }} - {{ if $stats.WorkoutType.IsDistance }} + {{ range $type, $PerMonth := $stats.Buckets }} + {{ if $type.IsDistance }} { - label: "{{ i18n $stats.WorkoutType.String }}", + label: "{{ i18n $type.String }}", data: [ - {{ range $y, $yv := $stats.PerMonth -}} - {{- range $m, $mv := $yv -}} - { x: "{{ $y }}-{{ printf "%02d" $m }}", y: {{ $mv.MaxSpeedKPH }} }, + {{- range $PerMonth -}} + { x: "{{ .Bucket }}", y: {{ .MaxSpeedKPH }} }, {{ end -}} - {{- end }} ], }, {{ end }}